<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"><title>Spring高级45讲【第四章】：WEB | 狼族少年、血狼</title><meta name="author" content="狼族少年、血狼"><meta name="copyright" content="狼族少年、血狼"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="DispatcherServlet及其重要组件DispatcherServlet初始化我们使用可支持内嵌web服务器的容器实现AnnotationConfigServletWebServerApplicationContext，这个容器不仅支持内嵌web服务器，也支持注解配置。 演示代码： 1234567891011121314151617181920212223242526272829publi">
<meta property="og:type" content="article">
<meta property="og:title" content="Spring高级45讲【第四章】：WEB">
<meta property="og:url" content="https://geekwolfman.github.io/2023/04/15/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB.html">
<meta property="og:site_name" content="狼族少年、血狼">
<meta property="og:description" content="DispatcherServlet及其重要组件DispatcherServlet初始化我们使用可支持内嵌web服务器的容器实现AnnotationConfigServletWebServerApplicationContext，这个容器不仅支持内嵌web服务器，也支持注解配置。 演示代码： 1234567891011121314151617181920212223242526272829publi">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/01cover.jpg">
<meta property="article:published_time" content="2023-04-15T14:06:32.000Z">
<meta property="article:modified_time" content="2023-04-15T14:25:52.925Z">
<meta property="article:author" content="狼族少年、血狼">
<meta property="article:tag" content="spring">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/01cover.jpg"><link rel="shortcut icon" href="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/config/avatar/avatar.png"><link rel="canonical" href="https://geekwolfman.github.io/2023/04/15/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB.html"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/db.json","preload":false,"languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false,
  percent: {
    toc: true,
    rightside: true,
  }
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'Spring高级45讲【第四章】：WEB',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2023-04-15 22:25:52'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
    win.getCSS = (url,id = false) => new Promise((resolve, reject) => {
      const link = document.createElement('link')
      link.rel = 'stylesheet'
      link.href = url
      if (id) link.id = id
      link.onerror = reject
      link.onload = link.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        link.onload = link.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(link)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><meta name="generator" content="Hexo 6.3.0"></head><body><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pace-js/themes/blue/pace-theme-minimal.min.css"/><script src="https://cdn.jsdelivr.net/npm/pace-js/pace.min.js"></script><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/config/avatar/avatar.png" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">57</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">14</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">9</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div><div class="menus_item"><a class="site-page" href="/gallery/"><i class="fa-fw fas fa-images"></i><span> 画廊</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-paper-plane"></i><span> 关于</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/01cover.jpg')"><nav id="nav"><span id="blog-info"><a href="/" title="狼族少年、血狼"><span class="site-name">狼族少年、血狼</span></a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search" href="javascript:void(0);"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div><div class="menus_item"><a class="site-page" href="/gallery/"><i class="fa-fw fas fa-images"></i><span> 画廊</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-paper-plane"></i><span> 关于</span></a></div></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">Spring高级45讲【第四章】：WEB</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2023-04-15T14:06:32.000Z" title="发表于 2023-04-15 22:06:32">2023-04-15</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2023-04-15T14:25:52.925Z" title="更新于 2023-04-15 22:25:52">2023-04-15</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6/">原理探究</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">26k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>127分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="Spring高级45讲【第四章】：WEB"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h1 id="DispatcherServlet及其重要组件"><a href="#DispatcherServlet及其重要组件" class="headerlink" title="DispatcherServlet及其重要组件"></a>DispatcherServlet及其重要组件</h1><h2 id="DispatcherServlet初始化"><a href="#DispatcherServlet初始化" class="headerlink" title="DispatcherServlet初始化"></a>DispatcherServlet初始化</h2><p>我们使用可支持内嵌web服务器的容器实现<code>AnnotationConfigServletWebServerApplicationContext</code>，这个容器不仅支持内嵌web服务器，也支持注解配置。</p>
<p>演示代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A20</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(A20.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">AnnotationConfigServletWebServerApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span></span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">AnnotationConfigServletWebServerApplicationContext</span>(WebConfig.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> TomcatServletWebServerFactory <span class="title function_">tomcatServletWebServerFactory</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">TomcatServletWebServerFactory</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DispatcherServlet <span class="title function_">dispatcherServlet</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DispatcherServlet</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DispatcherServletRegistrationBean <span class="title function_">dispatcherServletRegistrationBean</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DispatcherServletRegistrationBean</span>(dispatcherServlet, <span class="string">&quot;/&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果需要支持内嵌web容器的功能，配置类中有三项是必须配置的：</p>
<ol>
<li>内嵌的<code>web</code>容器工厂，例如<code>tomcat</code>、<code>jetty</code></li>
<li><code>DispatcherServlet</code>，负责拦截请求</li>
<li>注册器，注册<code>DispatcherServlet</code>,<code>Spring MVC</code>的入口</li>
</ol>
<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">[INFO ] 20:48:27.603 [main] o.s.b.w.e.tomcat.TomcatWebServer    - Tomcat initialized with port(s): 8080 (http) </span><br><span class="line">4月 02, 2023 8:48:27 下午 org.apache.coyote.AbstractProtocol init</span><br><span class="line">信息: Initializing ProtocolHandler [&quot;http-nio-8080&quot;]</span><br><span class="line">4月 02, 2023 8:48:27 下午 org.apache.catalina.core.StandardService startInternal</span><br><span class="line">信息: Starting service [Tomcat]</span><br><span class="line">4月 02, 2023 8:48:27 下午 org.apache.catalina.core.StandardEngine startInternal</span><br><span class="line">信息: Starting Servlet engine: [Apache Tomcat/9.0.53]</span><br><span class="line">4月 02, 2023 8:48:27 下午 org.apache.catalina.core.ApplicationContext log</span><br><span class="line">信息: Initializing Spring embedded WebApplicationContext</span><br><span class="line">[INFO ] 20:48:27.805 [main] o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 856 ms </span><br><span class="line">4月 02, 2023 8:48:27 下午 org.apache.coyote.AbstractProtocol start</span><br><span class="line">信息: Starting ProtocolHandler [&quot;http-nio-8080&quot;]</span><br><span class="line">[INFO ] 20:48:27.959 [main] o.s.b.w.e.tomcat.TomcatWebServer    - Tomcat started on port(s): 8080 (http) with context path &#x27;&#x27;</span><br></pre></td></tr></table></figure>

<p>我们发现<code>tomcat</code>容器和<code>spring</code>容器都已经初始化完毕。</p>
<p><code>DispatcherServlet</code>是由<code>spring</code>容器创建的，但是它的初始化是由tomcat来管理的，当用户首次访问tomcat服务器时，将会初始化<code>DispatcherServlet</code>。</p>
<p>清理一下控制台，访问<code>tomcat</code>服务器，查看打印日志：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">[INFO ] 20:55:07.911 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - Initializing Servlet &#x27;dispatcherServlet&#x27; </span><br><span class="line">[TRACE] 20:55:07.912 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - No MultipartResolver &#x27;multipartResolver&#x27; declared </span><br><span class="line">[TRACE] 20:55:07.916 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - No LocaleResolver &#x27;localeResolver&#x27;: using default [AcceptHeaderLocaleResolver] </span><br><span class="line">[TRACE] 20:55:07.917 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - No ThemeResolver &#x27;themeResolver&#x27;: using default [FixedThemeResolver] </span><br><span class="line">[TRACE] 20:55:08.243 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - No HandlerMappings declared for servlet &#x27;dispatcherServlet&#x27;: using default strategies from DispatcherServlet.properties </span><br><span class="line">[TRACE] 20:55:08.303 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - No HandlerAdapters declared for servlet &#x27;dispatcherServlet&#x27;: using default strategies from DispatcherServlet.properties </span><br><span class="line">[TRACE] 20:55:08.312 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - No HandlerExceptionResolvers declared in servlet &#x27;dispatcherServlet&#x27;: using default strategies from DispatcherServlet.properties </span><br><span class="line">[TRACE] 20:55:08.314 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - No RequestToViewNameTranslator &#x27;viewNameTranslator&#x27;: using default [DefaultRequestToViewNameTranslator] </span><br><span class="line">[TRACE] 20:55:08.326 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - No ViewResolvers declared for servlet &#x27;dispatcherServlet&#x27;: using default strategies from DispatcherServlet.properties </span><br><span class="line">[TRACE] 20:55:08.328 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - No FlashMapManager &#x27;flashMapManager&#x27;: using default [SessionFlashMapManager] </span><br><span class="line">[DEBUG] 20:55:08.329 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - enableLoggingRequestDetails=&#x27;false&#x27;: request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data </span><br><span class="line">[INFO ] 20:55:08.329 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - Completed initialization in 418 ms </span><br><span class="line">[TRACE] 20:55:08.339 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - GET &quot;/&quot;, parameters=&#123;&#125;, headers=&#123;masked&#125; in DispatcherServlet &#x27;dispatcherServlet&#x27; </span><br><span class="line">[WARN ] 20:55:08.344 [http-nio-8080-exec-1] o.s.web.servlet.PageNotFound        - No mapping for GET / </span><br><span class="line">[DEBUG] 20:55:08.346 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet   - Completed 404 NOT_FOUND, headers=&#123;&#125; </span><br><span class="line">[TRACE] 20:55:08.771 [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet   - GET &quot;/favicon.ico&quot;, parameters=&#123;&#125;, headers=&#123;masked&#125; in DispatcherServlet &#x27;dispatcherServlet&#x27; </span><br><span class="line">[WARN ] 20:55:08.772 [http-nio-8080-exec-2] o.s.web.servlet.PageNotFound        - No mapping for GET /favicon.ico </span><br><span class="line">[DEBUG] 20:55:08.772 [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet   - Completed 404 NOT_FOUND, headers=&#123;&#125; </span><br></pre></td></tr></table></figure>

<p>我们发现，第一行正在初始化<code>DispatcherServlet</code>，第2~10行是初始化组件，第12行表示已经完成了<code>DispatcherServlet</code>的初始化。</p>
<p>我们可以将断点打在<code>DispatcherServlet</code>中的<code>onRefresh()</code>方法上，启动服务器，发现一开始并没有停在断点处，当我们<code>tomcat</code>服务器：</p>
<p><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/1.png" alt="img"></p>
<p>查看调用链，我们发现<code>DispatcherServlet</code>的<code>onRefresh()</code>方法是在执行<code>Servlet</code>的<code>init()</code>方法时调用的，因此<code>DispatcherServlet</code>走的是<code>Servlet</code>的初始化流程。</p>
<p>目前是在首次访问tomcat服务器时，<code>DispatcherServlet</code>才会去初始化，我们可以通过<code>DispatcherServletRegistrationBean</code>的<code>setLoadOnStartup()</code>方法去更改。<code>setLoadOnStartup()</code>方法的默认值为-1，即首次访问tomcat服务器时才初始化<code>DispatcherServlet</code>，如果值大于0，则启动时就会初始化<code>DispatcherServlet</code>。如果有多个<code>Servlet</code>，此值代表初始化的优先级，值越小优先级越高。</p>
<p>修改<code>setLoadOnStartup()</code>方法的值：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> DispatcherServletRegistrationBean <span class="title function_">dispatcherServletRegistrationBean</span><span class="params">(DispatcherServlet dispatcherServlet)</span> &#123;</span><br><span class="line">    <span class="type">DispatcherServletRegistrationBean</span> <span class="variable">registrationBean</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DispatcherServletRegistrationBean</span>(dispatcherServlet, <span class="string">&quot;/&quot;</span>);</span><br><span class="line">    registrationBean.setLoadOnStartup(<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">return</span> registrationBean;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>重新运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">[INFO ] 21:09:59.471 [main] o.s.b.w.e.tomcat.TomcatWebServer    - Tomcat initialized with port(s): 8080 (http) </span><br><span class="line">4月 02, 2023 9:09:59 下午 org.apache.coyote.AbstractProtocol init</span><br><span class="line">信息: Initializing ProtocolHandler [&quot;http-nio-8080&quot;]</span><br><span class="line">4月 02, 2023 9:09:59 下午 org.apache.catalina.core.StandardService startInternal</span><br><span class="line">信息: Starting service [Tomcat]</span><br><span class="line">4月 02, 2023 9:09:59 下午 org.apache.catalina.core.StandardEngine startInternal</span><br><span class="line">信息: Starting Servlet engine: [Apache Tomcat/9.0.53]</span><br><span class="line">4月 02, 2023 9:09:59 下午 org.apache.catalina.core.ApplicationContext log</span><br><span class="line">信息: Initializing Spring embedded WebApplicationContext</span><br><span class="line">[INFO ] 21:09:59.686 [main] o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 888 ms </span><br><span class="line">4月 02, 2023 9:09:59 下午 org.apache.coyote.AbstractProtocol start</span><br><span class="line">信息: Starting ProtocolHandler [&quot;http-nio-8080&quot;]</span><br><span class="line">4月 02, 2023 9:09:59 下午 org.apache.catalina.core.ApplicationContext log</span><br><span class="line">信息: Initializing Spring DispatcherServlet &#x27;dispatcherServlet&#x27;</span><br><span class="line">[INFO ] 21:09:59.838 [main] o.s.web.servlet.DispatcherServlet   - Initializing Servlet &#x27;dispatcherServlet&#x27; </span><br><span class="line">[TRACE] 21:09:59.838 [main] o.s.web.servlet.DispatcherServlet   - No MultipartResolver &#x27;multipartResolver&#x27; declared </span><br><span class="line">[TRACE] 21:09:59.841 [main] o.s.web.servlet.DispatcherServlet   - No LocaleResolver &#x27;localeResolver&#x27;: using default [AcceptHeaderLocaleResolver] </span><br><span class="line">[TRACE] 21:09:59.843 [main] o.s.web.servlet.DispatcherServlet   - No ThemeResolver &#x27;themeResolver&#x27;: using default [FixedThemeResolver] </span><br><span class="line">[TRACE] 21:10:00.337 [main] o.s.web.servlet.DispatcherServlet   - No HandlerMappings declared for servlet &#x27;dispatcherServlet&#x27;: using default strategies from DispatcherServlet.properties </span><br><span class="line">[TRACE] 21:10:00.393 [main] o.s.web.servlet.DispatcherServlet   - No HandlerAdapters declared for servlet &#x27;dispatcherServlet&#x27;: using default strategies from DispatcherServlet.properties </span><br><span class="line">[TRACE] 21:10:00.402 [main] o.s.web.servlet.DispatcherServlet   - No HandlerExceptionResolvers declared in servlet &#x27;dispatcherServlet&#x27;: using default strategies from DispatcherServlet.properties </span><br><span class="line">[TRACE] 21:10:00.403 [main] o.s.web.servlet.DispatcherServlet   - No RequestToViewNameTranslator &#x27;viewNameTranslator&#x27;: using default [DefaultRequestToViewNameTranslator] </span><br><span class="line">[TRACE] 21:10:00.410 [main] o.s.web.servlet.DispatcherServlet   - No ViewResolvers declared for servlet &#x27;dispatcherServlet&#x27;: using default strategies from DispatcherServlet.properties </span><br><span class="line">[TRACE] 21:10:00.412 [main] o.s.web.servlet.DispatcherServlet   - No FlashMapManager &#x27;flashMapManager&#x27;: using default [SessionFlashMapManager] </span><br><span class="line">[DEBUG] 21:10:00.412 [main] o.s.web.servlet.DispatcherServlet   - enableLoggingRequestDetails=&#x27;false&#x27;: request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data </span><br><span class="line">[INFO ] 21:10:00.412 [main] o.s.web.servlet.DispatcherServlet   - Completed initialization in 574 ms </span><br><span class="line">[INFO ] 21:10:00.414 [main] o.s.b.w.e.tomcat.TomcatWebServer    - Tomcat started on port(s): 8080 (http) with context path &#x27;&#x27; </span><br></pre></td></tr></table></figure>

<p>我们发现容器启动时，<code>DispatcherServlet</code>就已经初始化完毕。</p>
<p>在实际情况下，一般将配置放在配置文件中，而不是写入代码，因此我们将配置抽取成配置文件。我们可以使用<code>@PropertySource</code>注解来读取类路径下的配置文件：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@PropertySource(&quot;classpath:application.properties&quot;)</span></span><br></pre></td></tr></table></figure>

<p><code>@EnableConfigurationProperties</code>注解提供将配置进行批量绑定的功能：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@EnableConfigurationProperties(&#123;WebMvcProperties.class, ServerProperties.class&#125;)</span></span><br></pre></td></tr></table></figure>

<p>例如<code>WebMvcProperties.class</code>可以绑定以<code>spring.mvc</code>的配置项，查看<code>WebMvcProperties.class</code>源码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ConfigurationProperties(</span></span><br><span class="line"><span class="meta">    prefix = &quot;spring.mvc&quot;</span></span><br><span class="line"><span class="meta">)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebMvcProperties</span> &#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>绑定的对象会作为Bean注入到容器中，我们直接使用即可，更改后的<code>WebConfig</code>类如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan</span></span><br><span class="line"><span class="meta">@PropertySource(&quot;classpath:application.properties&quot;)</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(&#123;WebMvcProperties.class, ServerProperties.class&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ⬅️内嵌 web 容器工厂</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> TomcatServletWebServerFactory <span class="title function_">tomcatServletWebServerFactory</span><span class="params">(ServerProperties serverProperties)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">TomcatServletWebServerFactory</span>(serverProperties.getPort());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ⬅️创建 DispatcherServlet</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DispatcherServlet <span class="title function_">dispatcherServlet</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DispatcherServlet</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ⬅️注册 DispatcherServlet, Spring MVC 的入口</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DispatcherServletRegistrationBean <span class="title function_">dispatcherServletRegistrationBean</span><span class="params">(</span></span><br><span class="line"><span class="params">            DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties)</span> &#123;</span><br><span class="line">        <span class="type">DispatcherServletRegistrationBean</span> <span class="variable">registrationBean</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DispatcherServletRegistrationBean</span>(dispatcherServlet, <span class="string">&quot;/&quot;</span>);</span><br><span class="line">        registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());</span><br><span class="line">        <span class="keyword">return</span> registrationBean;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>重新运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">[INFO ] 21:21:23.218 [main] o.s.b.w.e.tomcat.TomcatWebServer    - Tomcat initialized with port(s): 8080 (http) </span><br><span class="line">4月 02, 2023 9:21:23 下午 org.apache.coyote.AbstractProtocol init</span><br><span class="line">信息: Initializing ProtocolHandler [&quot;http-nio-8080&quot;]</span><br><span class="line">4月 02, 2023 9:21:23 下午 org.apache.catalina.core.StandardService startInternal</span><br><span class="line">信息: Starting service [Tomcat]</span><br><span class="line">4月 02, 2023 9:21:23 下午 org.apache.catalina.core.StandardEngine startInternal</span><br><span class="line">信息: Starting Servlet engine: [Apache Tomcat/9.0.53]</span><br><span class="line">4月 02, 2023 9:21:23 下午 org.apache.catalina.core.ApplicationContext log</span><br><span class="line">信息: Initializing Spring embedded WebApplicationContext</span><br><span class="line">[INFO ] 21:21:23.412 [main] o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1279 ms </span><br><span class="line">4月 02, 2023 9:21:23 下午 org.apache.coyote.AbstractProtocol start</span><br><span class="line">信息: Starting ProtocolHandler [&quot;http-nio-8080&quot;]</span><br><span class="line">4月 02, 2023 9:21:23 下午 org.apache.catalina.core.ApplicationContext log</span><br><span class="line">信息: Initializing Spring DispatcherServlet &#x27;dispatcherServlet&#x27;</span><br><span class="line">[INFO ] 21:21:23.552 [main] o.s.web.servlet.DispatcherServlet   - Initializing Servlet &#x27;dispatcherServlet&#x27; </span><br><span class="line">[TRACE] 21:21:23.553 [main] o.s.web.servlet.DispatcherServlet   - No MultipartResolver &#x27;multipartResolver&#x27; declared </span><br><span class="line">[TRACE] 21:21:23.556 [main] o.s.web.servlet.DispatcherServlet   - No LocaleResolver &#x27;localeResolver&#x27;: using default [AcceptHeaderLocaleResolver] </span><br><span class="line">[TRACE] 21:21:23.558 [main] o.s.web.servlet.DispatcherServlet   - No ThemeResolver &#x27;themeResolver&#x27;: using default [FixedThemeResolver] </span><br><span class="line">[TRACE] 21:21:23.894 [main] o.s.web.servlet.DispatcherServlet   - No HandlerMappings declared for servlet &#x27;dispatcherServlet&#x27;: using default strategies from DispatcherServlet.properties </span><br><span class="line">[TRACE] 21:21:23.944 [main] o.s.web.servlet.DispatcherServlet   - No HandlerAdapters declared for servlet &#x27;dispatcherServlet&#x27;: using default strategies from DispatcherServlet.properties </span><br><span class="line">[TRACE] 21:21:23.955 [main] o.s.web.servlet.DispatcherServlet   - No HandlerExceptionResolvers declared in servlet &#x27;dispatcherServlet&#x27;: using default strategies from DispatcherServlet.properties </span><br><span class="line">[TRACE] 21:21:23.957 [main] o.s.web.servlet.DispatcherServlet   - No RequestToViewNameTranslator &#x27;viewNameTranslator&#x27;: using default [DefaultRequestToViewNameTranslator] </span><br><span class="line">[TRACE] 21:21:23.965 [main] o.s.web.servlet.DispatcherServlet   - No ViewResolvers declared for servlet &#x27;dispatcherServlet&#x27;: using default strategies from DispatcherServlet.properties </span><br><span class="line">[TRACE] 21:21:23.968 [main] o.s.web.servlet.DispatcherServlet   - No FlashMapManager &#x27;flashMapManager&#x27;: using default [SessionFlashMapManager] </span><br><span class="line">[DEBUG] 21:21:23.968 [main] o.s.web.servlet.DispatcherServlet   - enableLoggingRequestDetails=&#x27;false&#x27;: request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data </span><br><span class="line">[INFO ] 21:21:23.968 [main] o.s.web.servlet.DispatcherServlet   - Completed initialization in 416 ms </span><br><span class="line">[INFO ] 21:21:23.969 [main] o.s.b.w.e.tomcat.TomcatWebServer    - Tomcat started on port(s): 8080 (http) with context path &#x27;&#x27; </span><br></pre></td></tr></table></figure>

<p>发现我们的配置文件已经生效。</p>
<h2 id="DispatcherServlet初始化过程"><a href="#DispatcherServlet初始化过程" class="headerlink" title="DispatcherServlet初始化过程"></a>DispatcherServlet初始化过程</h2><p><code>DispatcherServlet</code>初始化过程主要在<code>onRefresh()</code>方法中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onRefresh</span><span class="params">(ApplicationContext context)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.initStrategies(context);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>进入initStrategies()方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 此方法初始化了多种组件</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initStrategies</span><span class="params">(ApplicationContext context)</span> &#123;</span><br><span class="line">    <span class="comment">// 初始化文件解析器</span></span><br><span class="line">    <span class="built_in">this</span>.initMultipartResolver(context);</span><br><span class="line">    <span class="comment">// 初始化本地化信息，初始化语言、地区</span></span><br><span class="line">    <span class="built_in">this</span>.initLocaleResolver(context);</span><br><span class="line">    <span class="built_in">this</span>.initThemeResolver(context);</span><br><span class="line">    <span class="comment">// 初始化路径映射器</span></span><br><span class="line">    <span class="built_in">this</span>.initHandlerMappings(context);</span><br><span class="line">    <span class="comment">// 初始化控制器适配器</span></span><br><span class="line">    <span class="built_in">this</span>.initHandlerAdapters(context);</span><br><span class="line">    <span class="comment">// 初始化异常解析器</span></span><br><span class="line">    <span class="built_in">this</span>.initHandlerExceptionResolvers(context);</span><br><span class="line">    <span class="built_in">this</span>.initRequestToViewNameTranslator(context);</span><br><span class="line">    <span class="built_in">this</span>.initViewResolvers(context);</span><br><span class="line">    <span class="built_in">this</span>.initFlashMapManager(context);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ol>
<li><strong>RequestMappingHandlerMapping</strong></li>
</ol>
<p><code>RequestMappingHandlerMapping</code>的主要作用是建立请求路径与控制器的映射关系，它的主要工作流程如下：</p>
<ol>
<li>扫描启动类下所有带有<code>@Controller</code>注解的类</li>
<li>解析类中带有<code>@GetMapping</code>、<code>@PostMapping</code>等注解的方法并建立请求路径与方法的映射。</li>
</ol>
<p>由于<code>RequestMappingHandlerMapping</code>是由<code>tomcat</code>服务器管理的，并没有加入<code>spring</code>容器中，为了方便演示，我们添加自己创建的<code>RequestMappingHandlerMapping</code>，在<code>WebConfig</code>类中添加<code>Bean</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> RequestMappingHandlerMapping <span class="title function_">requestMappingHandlerMapping</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RequestMappingHandlerMapping</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>添加<code>Controller1</code>类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Controller1</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(Controller1.class);</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/test1&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">test1</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        log.debug(<span class="string">&quot;test1()&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping(&quot;/test2&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">test2</span><span class="params">(<span class="meta">@RequestParam(&quot;name&quot;)</span> String name)</span> &#123;</span><br><span class="line">        log.debug(<span class="string">&quot;test2(&#123;&#125;)&quot;</span>, name);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PutMapping(&quot;/test3&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">test3</span><span class="params">(<span class="meta">@Token</span> String token)</span> &#123;</span><br><span class="line">        log.debug(<span class="string">&quot;test3(&#123;&#125;)&quot;</span>, token);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping(&quot;/test4&quot;)</span></span><br><span class="line"><span class="comment">//    @ResponseBody</span></span><br><span class="line">    <span class="meta">@Yml</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">test4</span><span class="params">()</span> &#123;</span><br><span class="line">        log.debug(<span class="string">&quot;test4&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;张三&quot;</span>, <span class="number">18</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">User</span><span class="params">(String name, <span class="type">int</span> age)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">            <span class="built_in">this</span>.age = age;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getAge</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> age;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAge</span><span class="params">(<span class="type">int</span> age)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.age = age;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Yaml</span>().dump(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;张三&quot;</span>, <span class="number">18</span>));</span><br><span class="line">        System.out.println(str);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在启动类中获取<code>RequestMappingHandlerMapping</code>实例，并打印映射：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 作用 解析 @RequestMapping 以及派生注解，生成路径与控制器方法的映射关系, 在初始化时就生成</span></span><br><span class="line"><span class="type">RequestMappingHandlerMapping</span> <span class="variable">handlerMapping</span> <span class="operator">=</span> context.getBean(RequestMappingHandlerMapping.class);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取映射结果 RequestMappingInfo为请求方法与路径信息，HandlerMethod为映射的方法</span></span><br><span class="line">Map&lt;RequestMappingInfo, HandlerMethod&gt; handlerMethods = handlerMapping.getHandlerMethods();</span><br><span class="line">handlerMethods.forEach((k, v) -&gt; &#123;</span><br><span class="line">    System.out.println(k + <span class="string">&quot;=&quot;</span> + v);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;GET [/test1]&#125;=com.itheima.a20.Controller1#test1()</span><br><span class="line">&#123;PUT [/test3]&#125;=com.itheima.a20.Controller1#test3(String)</span><br><span class="line">&#123; [/test4]&#125;=com.itheima.a20.Controller1#test4()</span><br><span class="line">&#123;POST [/test2]&#125;=com.itheima.a20.Controller1#test2(String)</span><br></pre></td></tr></table></figure>

<p>发现所有的映射都已经被打印出来。</p>
<p>我们可以模拟一个请求，并打印其信息：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 返回值是一个执行器链，包含拦截器</span></span><br><span class="line"><span class="type">HandlerExecutionChain</span> <span class="variable">chain</span> <span class="operator">=</span> handlerMapping.getHandler(<span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>(<span class="string">&quot;GET&quot;</span>, <span class="string">&quot;/test4&quot;</span>));</span><br><span class="line">System.out.println(chain);</span><br></pre></td></tr></table></figure>

<p>控制台输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">HandlerExecutionChain with [com.itheima.a20.Controller1#test4()] and 0 interceptors</span><br></pre></td></tr></table></figure>

<p>我们可以发现最终执行地方法逻辑为<code>com.itheima.a20.Controller1#test4()</code>，并且拦截器数量为0。</p>
<ol start="2">
<li><strong>RequestMappingHandlerAdapter</strong></li>
</ol>
<p><code>RequestMappingHandlerAdapter</code>的主要作用是调用控制器的方法。</p>
<p>由于<code>RequestMappingHandlerAdapter</code>是由<code>tomcat</code>服务器管理的，并没有加入<code>spring</code>容器中，为了方便演示，我们添加自己创建的<code>RequestMappingHandlerAdapter</code>，在<code>WebConfig</code>类中添加<code>Bean</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> RequestMappingHandlerAdapter <span class="title function_">requestMappingHandlerAdapter</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RequestMappingHandlerAdapter</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>由于<code>RequestMappingHandlerAdapter</code>中的重要方法<code>invokeHandlerMethod()</code>被<code>protected</code>关键字修饰，不能调用，因此我们创建一个子类去更改它的访问修饰符：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyRequestMappingHandlerAdapter</span> <span class="keyword">extends</span> <span class="title class_">RequestMappingHandlerAdapter</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">invokeHandlerMethod</span><span class="params">(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>.invokeHandlerMethod(request, response, handlerMethod);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>修改<code>WebConfig</code>类中<code>RequestMappingHandlerAdapter</code>类型的<code>Bean</code>注入：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> MyRequestMappingHandlerAdapter <span class="title function_">requestMappingHandlerAdapter</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">MyRequestMappingHandlerAdapter</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在启动类中添加测试代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>(<span class="string">&quot;POST&quot;</span>, <span class="string">&quot;/test2&quot;</span>);</span><br><span class="line">request.setParameter(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line"><span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line"><span class="type">HandlerExecutionChain</span> <span class="variable">chain</span> <span class="operator">=</span> handlerMapping.getHandler(request);</span><br><span class="line">System.out.println(chain);</span><br><span class="line"></span><br><span class="line">System.out.println(<span class="string">&quot;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&quot;</span>);</span><br><span class="line"><span class="comment">// HandlerAdapter 作用: 调用控制器方法</span></span><br><span class="line"><span class="type">MyRequestMappingHandlerAdapter</span> <span class="variable">handlerAdapter</span> <span class="operator">=</span> context.getBean(MyRequestMappingHandlerAdapter.class);</span><br><span class="line">handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">HandlerExecutionChain with [com.itheima.a20.Controller1#test2(String)] and <span class="number">0</span> interceptors</span><br><span class="line">&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;</span><br><span class="line">[DEBUG] <span class="number">22</span>:<span class="number">07</span>:<span class="number">38.394</span> [main] com.itheima.a20.Controller1         - test2(张三) </span><br></pre></td></tr></table></figure>

<p>我们发现<code>RequestMappingHandlerAdapter</code>正常调用了方法，并且成功解析了参数，说明<code>@RequestParam</code>成功被解析了，实际上<code>@RequestParam</code>注解的解析是由<code>RequestMappingHandlerAdapter</code>中的其他组件来实现的。</p>
<p>实际上<code>RequestMappingHandlerAdapter</code>中有很多的参数解析器，解析<code>@RequestParam</code>、<code>@RequestBody</code>等注解，我们可以打印一下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (HandlerMethodArgumentResolver resolver : handlerAdapter.getArgumentResolvers()) &#123;</span><br><span class="line">    System.out.println(resolver);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@58516c91</span><br><span class="line">org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@7c129ef6</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@42d73c61</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@5a8cbffe</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@96a75da</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@61e7bf2f</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@1a28b346</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@25e49cb2</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@7f7af971</span><br><span class="line">org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@23382f76</span><br><span class="line">org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@7c551ad4</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@7d5508e0</span><br><span class="line">org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@554cd74a</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@37ed010a</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@633a2e99</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@367d2816</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@5b84f14</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@5a82ebf8</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@68fe48d7</span><br><span class="line">org.springframework.web.method.annotation.ModelMethodProcessor@379ce046</span><br><span class="line">org.springframework.web.method.annotation.MapMethodProcessor@701bc94e</span><br><span class="line">org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@3d8b319e</span><br><span class="line">org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@27a97e08</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@77e7246b</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@5918c260</span><br><span class="line">org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@3d7b1f1c</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@51ce6f85</span><br></pre></td></tr></table></figure>

<p>我们可以发现有很多的参数解析器，其中就有解析<code>@RequestParam</code>注解的<code>RequestParamMethodArgumentResolver</code>。</p>
<p>除了参数解析器，还有返回值处理器，用来解析不同类型的返回值，比如字符串、对象、<code>ModelAndView</code>…最终返回值会被统一转换为<code>ModelAndView</code>。我们可以打印一下返回值处理器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (HandlerMethodReturnValueHandler handler : handlerAdapter.getReturnValueHandlers()) &#123;</span><br><span class="line">    System.out.println(handler);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@1f44ddab</span><br><span class="line">org.springframework.web.method.annotation.ModelMethodProcessor@5017e1</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@65b66b08</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@4726927c</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@7eb6b6b6</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@7ed9499e</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler@28e19366</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler@5b275174</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler@10ef5fa0</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler@244e619a</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@10acd6</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@61dde151</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler@b25b095</span><br><span class="line">org.springframework.web.method.annotation.MapMethodProcessor@5cb042da</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@59c33386</span><br></pre></td></tr></table></figure>

<p><strong>自定义参数解析器</strong></p>
<p>在<code>Controller</code>中定义方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@PutMapping(&quot;/test3&quot;)</span></span><br><span class="line"><span class="keyword">public</span> ModelAndView <span class="title function_">test3</span><span class="params">(<span class="meta">@Token</span> String token)</span> &#123;</span><br><span class="line">    log.debug(<span class="string">&quot;test3(&#123;&#125;)&quot;</span>, token);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>@Token</code>是我们自定义的注解：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target(ElementType.PARAMETER)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Token &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>实现自定义的<code>TokenArgumentResolver</code>，需要实现<code>HandlerMethodArgumentResolver</code>接口：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TokenArgumentResolver</span> <span class="keyword">implements</span> <span class="title class_">HandlerMethodArgumentResolver</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 是否支持某个参数，参数上是否有目标注解</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">supportsParameter</span><span class="params">(MethodParameter parameter)</span> &#123;</span><br><span class="line">        <span class="type">Token</span> <span class="variable">token</span> <span class="operator">=</span> parameter.getParameterAnnotation(Token.class);</span><br><span class="line">        <span class="keyword">return</span> token != <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 解析参数</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">resolveArgument</span><span class="params">(MethodParameter parameter, ModelAndViewContainer mavContainer,</span></span><br><span class="line"><span class="params">                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="keyword">return</span> webRequest.getHeader(<span class="string">&quot;token&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接下来需要将<code>TokenArgumentResolver</code>加入到<code>RequestMappingHandlerAdapter</code>中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> MyRequestMappingHandlerAdapter <span class="title function_">requestMappingHandlerAdapter</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">TokenArgumentResolver</span> <span class="variable">tokenArgumentResolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TokenArgumentResolver</span>();</span><br><span class="line">    <span class="type">MyRequestMappingHandlerAdapter</span> <span class="variable">handlerAdapter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyRequestMappingHandlerAdapter</span>();</span><br><span class="line">    handlerAdapter.setCustomArgumentResolvers(List.of(tokenArgumentResolver));</span><br><span class="line">    <span class="keyword">return</span> handlerAdapter;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>模拟请求，进行测试：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>(<span class="string">&quot;PUT&quot;</span>, <span class="string">&quot;/test3&quot;</span>);</span><br><span class="line">request.setParameter(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">request.addHeader(<span class="string">&quot;token&quot;</span>, <span class="string">&quot;某个令牌&quot;</span>);</span><br><span class="line"><span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line"><span class="type">HandlerExecutionChain</span> <span class="variable">chain</span> <span class="operator">=</span> handlerMapping.getHandler(request);</span><br><span class="line">System.out.println(chain);</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[DEBUG] 11:03:10.307 [main] com.itheima.a20.Controller1         - test3(某个令牌) </span><br></pre></td></tr></table></figure>

<p>发现能正常解析我们的<code>@Token</code>注解。</p>
<p><strong>自定义返回值处理器</strong></p>
<p>返回值处理器可以解析不同类型的返回值，比如字符串、对象、<code>ModelAndView</code>…除此之外还可以解析方法的注解，比如解析<code>@ResponseBody</code>。</p>
<p>在<code>Controller</code>中定义方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestMapping(&quot;/test4&quot;)</span></span><br><span class="line"><span class="meta">@Yml</span></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">test4</span><span class="params">()</span> &#123;</span><br><span class="line">    log.debug(<span class="string">&quot;test4&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;张三&quot;</span>, <span class="number">18</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>@Yml</code>是我们自定义的注解：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target(ElementType.METHOD)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Yml &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>@Yml</code>注解将返回值转换为<code>Yml</code>格式，我们可以使用<code>org.yaml.snakeyaml.Yaml</code>工具类进行转换：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">User</span><span class="params">(String name, <span class="type">int</span> age)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">        <span class="built_in">this</span>.age = age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getAge</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAge</span><span class="params">(<span class="type">int</span> age)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.age = age;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Yaml</span>().dump(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;张三&quot;</span>, <span class="number">18</span>));</span><br><span class="line">    System.out.println(str);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>控制台输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">!!com.itheima.a20.Controller1$User &#123;age: 18, name: 张三&#125;</span><br></pre></td></tr></table></figure>

<p>实现自定义的<code>YmlReturnValueHandler</code>，需要实现<code>HandlerMethodReturnValueHandler</code>接口：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">YmlReturnValueHandler</span> <span class="keyword">implements</span> <span class="title class_">HandlerMethodReturnValueHandler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 是否支持某个参数，参数上是否有目标注解</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">supportsReturnType</span><span class="params">(MethodParameter returnType)</span> &#123;</span><br><span class="line">        <span class="type">Yml</span> <span class="variable">yml</span> <span class="operator">=</span> returnType.getMethodAnnotation(Yml.class);</span><br><span class="line">        <span class="keyword">return</span> yml != <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 处理返回值</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleReturnValue</span><span class="params">(Object returnValue, MethodParameter returnType,</span></span><br><span class="line"><span class="params">                                  ModelAndViewContainer mavContainer, NativeWebRequest webRequest)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 1. 转换返回结果为 yaml 字符串</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Yaml</span>().dump(returnValue);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 将 yaml 字符串写入响应</span></span><br><span class="line">        <span class="type">HttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> webRequest.getNativeResponse(HttpServletResponse.class);</span><br><span class="line">        response.setContentType(<span class="string">&quot;text/plain;charset=utf-8&quot;</span>);</span><br><span class="line">        response.getWriter().print(str);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 设置请求已经处理完毕</span></span><br><span class="line">        mavContainer.setRequestHandled(<span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接下来需要将<code>YmlReturnValueHandler</code>加入到<code>RequestMappingHandlerAdapter</code>中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> MyRequestMappingHandlerAdapter <span class="title function_">requestMappingHandlerAdapter</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">TokenArgumentResolver</span> <span class="variable">tokenArgumentResolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TokenArgumentResolver</span>();</span><br><span class="line">    <span class="type">YmlReturnValueHandler</span> <span class="variable">ymlReturnValueHandler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">YmlReturnValueHandler</span>();</span><br><span class="line">    <span class="type">MyRequestMappingHandlerAdapter</span> <span class="variable">handlerAdapter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyRequestMappingHandlerAdapter</span>();</span><br><span class="line">    handlerAdapter.setCustomArgumentResolvers(List.of(tokenArgumentResolver));</span><br><span class="line">    handlerAdapter.setCustomReturnValueHandlers(List.of(ymlReturnValueHandler));</span><br><span class="line">    <span class="keyword">return</span> handlerAdapter;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>模拟请求，进行测试：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>(<span class="string">&quot;GET&quot;</span>, <span class="string">&quot;/test4&quot;</span>);</span><br><span class="line"><span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line"><span class="type">HandlerExecutionChain</span> <span class="variable">chain</span> <span class="operator">=</span> handlerMapping.getHandler(request);</span><br><span class="line"><span class="comment">// HandlerAdapter 作用: 调用控制器方法</span></span><br><span class="line"><span class="type">MyRequestMappingHandlerAdapter</span> <span class="variable">handlerAdapter</span> <span class="operator">=</span> context.getBean(MyRequestMappingHandlerAdapter.class);</span><br><span class="line">handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());</span><br><span class="line"><span class="comment">// 检查响应</span></span><br><span class="line"><span class="type">byte</span>[] content = response.getContentAsByteArray();</span><br><span class="line">System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(content, StandardCharsets.UTF_8));</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">!!com.itheima.a20.Controller1$User &#123;age: 18, name: 张三&#125;</span><br></pre></td></tr></table></figure>

<p>发现能正常解析我们的<code>@Yml</code>注解。</p>
<h1 id="参数解析器"><a href="#参数解析器" class="headerlink" title="参数解析器"></a>参数解析器</h1><p><code>RequestMappingHandlerAdapter</code>自带的参数解析器：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@abbc908</span><br><span class="line">org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@44afefd5</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@9a7a808</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@72209d93</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@2687f956</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@1ded7b14</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@29be7749</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5f84abe8</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@4650a407</span><br><span class="line">org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@30135202</span><br><span class="line">org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@6a4d7f76</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@10ec523c</span><br><span class="line">org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@53dfacba</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@79767781</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@78411116</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@aced190</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@245a060f</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@6edaa77a</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@1e63d216</span><br><span class="line">org.springframework.web.method.annotation.ModelMethodProcessor@62ddd21b</span><br><span class="line">org.springframework.web.method.annotation.MapMethodProcessor@16c3ca31</span><br><span class="line">org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@2d195ee4</span><br><span class="line">org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2d6aca33</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@21ab988f</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@29314cc9</span><br><span class="line">org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@4e38d975</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@35f8a9d3</span><br></pre></td></tr></table></figure>

<p>准备测试代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A21</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">AnnotationConfigApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AnnotationConfigApplicationContext</span>(WebConfig.class);</span><br><span class="line">        <span class="type">DefaultListableBeanFactory</span> <span class="variable">beanFactory</span> <span class="operator">=</span> context.getDefaultListableBeanFactory();</span><br><span class="line">        <span class="comment">// 准备测试 Request</span></span><br><span class="line">        <span class="type">HttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> mockRequest();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 要点1. 控制器方法被封装为 HandlerMethod</span></span><br><span class="line">        <span class="type">HandlerMethod</span> <span class="variable">handlerMethod</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(<span class="keyword">new</span> <span class="title class_">Controller</span>(), Controller.class.getMethod(<span class="string">&quot;test&quot;</span>, String.class, String.class, <span class="type">int</span>.class, String.class, MultipartFile.class, <span class="type">int</span>.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 要点2. 准备对象绑定与类型转换</span></span><br><span class="line">        <span class="type">ServletRequestDataBinderFactory</span> <span class="variable">factory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletRequestDataBinderFactory</span>(<span class="literal">null</span>, <span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果</span></span><br><span class="line">        <span class="type">ModelAndViewContainer</span> <span class="variable">container</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ModelAndViewContainer</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 要点4. 解析每个参数值</span></span><br><span class="line">        <span class="keyword">for</span> (MethodParameter parameter : handlerMethod.getMethodParameters()) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">annotation</span> <span class="operator">=</span> Arrays.stream(parameter.getParameterAnnotations()).map(a -&gt; a.annotationType().getSimpleName()).collect(Collectors.joining());</span><br><span class="line">            <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> annotation.equals(<span class="string">&quot;&quot;</span>) ? <span class="string">&quot;&quot;</span> : <span class="string">&quot;@&quot;</span> + annotation + <span class="string">&quot; &quot;</span>;</span><br><span class="line">            parameter.initParameterNameDiscovery(<span class="keyword">new</span> <span class="title class_">DefaultParameterNameDiscoverer</span>());</span><br><span class="line">            System.out.println(<span class="string">&quot;[&quot;</span> + parameter.getParameterIndex() + <span class="string">&quot;] &quot;</span> + str + parameter.getParameterType().getSimpleName() + <span class="string">&quot; &quot;</span> + parameter.getParameterName());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> HttpServletRequest <span class="title function_">mockRequest</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        request.setParameter(<span class="string">&quot;name1&quot;</span>, <span class="string">&quot;zhangsan&quot;</span>);</span><br><span class="line">        request.setParameter(<span class="string">&quot;name2&quot;</span>, <span class="string">&quot;lisi&quot;</span>);</span><br><span class="line">        request.addPart(<span class="keyword">new</span> <span class="title class_">MockPart</span>(<span class="string">&quot;file&quot;</span>, <span class="string">&quot;abc&quot;</span>, <span class="string">&quot;hello&quot;</span>.getBytes(StandardCharsets.UTF_8)));</span><br><span class="line">        Map&lt;String, String&gt; map = <span class="keyword">new</span> <span class="title class_">AntPathMatcher</span>().extractUriTemplateVariables(<span class="string">&quot;/test/&#123;id&#125;&quot;</span>, <span class="string">&quot;/test/123&quot;</span>);</span><br><span class="line">        request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);</span><br><span class="line">        request.setContentType(<span class="string">&quot;application/json&quot;</span>);</span><br><span class="line">        request.setCookies(<span class="keyword">new</span> <span class="title class_">Cookie</span>(<span class="string">&quot;token&quot;</span>, <span class="string">&quot;123456&quot;</span>));</span><br><span class="line">        request.setParameter(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">        request.setParameter(<span class="string">&quot;age&quot;</span>, <span class="string">&quot;18&quot;</span>);</span><br><span class="line">        request.setContent(<span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">                    &#123;</span></span><br><span class="line"><span class="string">                        &quot;name&quot;:&quot;李四&quot;,</span></span><br><span class="line"><span class="string">                        &quot;age&quot;:20</span></span><br><span class="line"><span class="string">                    &#125;</span></span><br><span class="line"><span class="string">                &quot;&quot;&quot;</span>.getBytes(StandardCharsets.UTF_8));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">StandardServletMultipartResolver</span>().resolveMultipart(request);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller</span> &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">(</span></span><br><span class="line"><span class="params">                <span class="meta">@RequestParam(&quot;name1&quot;)</span> String name1, // name1=张三</span></span><br><span class="line"><span class="params">                String name2,                        // name2=李四</span></span><br><span class="line"><span class="params">                <span class="meta">@RequestParam(&quot;age&quot;)</span> <span class="type">int</span> age,        // age=<span class="number">18</span></span></span><br><span class="line"><span class="params">                <span class="meta">@RequestParam(name = &quot;home&quot;, defaultValue = &quot;$&#123;JAVA_HOME&#125;&quot;)</span> String home1, // spring 获取数据</span></span><br><span class="line"><span class="params">                <span class="meta">@RequestParam(&quot;file&quot;)</span> MultipartFile file, // 上传文件</span></span><br><span class="line"><span class="params">                <span class="meta">@PathVariable(&quot;id&quot;)</span> <span class="type">int</span> id,               //  /test/<span class="number">124</span>   /test/&#123;id&#125;</span></span><br><span class="line"><span class="params">                <span class="meta">@RequestHeader(&quot;Content-Type&quot;)</span> String header,</span></span><br><span class="line"><span class="params">                <span class="meta">@CookieValue(&quot;token&quot;)</span> String token,</span></span><br><span class="line"><span class="params">                <span class="meta">@Value(&quot;$&#123;JAVA_HOME&#125;&quot;)</span> String home2, // spring 获取数据  $&#123;&#125; #&#123;&#125;</span></span><br><span class="line"><span class="params">                HttpServletRequest request,          // request, response, session ...</span></span><br><span class="line"><span class="params">                <span class="meta">@ModelAttribute(&quot;abc&quot;)</span> User user1,          // name=zhang&amp;age=<span class="number">18</span></span></span><br><span class="line"><span class="params">                User user2,                          // name=zhang&amp;age=<span class="number">18</span></span></span><br><span class="line"><span class="params">                <span class="meta">@RequestBody</span> User user3              // json</span></span><br><span class="line"><span class="params">        )</span> &#123;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getAge</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> age;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAge</span><span class="params">(<span class="type">int</span> age)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.age = age;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;User&#123;&quot;</span> +</span><br><span class="line">                    <span class="string">&quot;name=&#x27;&quot;</span> + name + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                    <span class="string">&quot;, age=&quot;</span> + age +</span><br><span class="line">                    <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">[0] @RequestParam String name1</span><br><span class="line">[1] String name2</span><br><span class="line">[2] @RequestParam int age</span><br><span class="line">[3] @RequestParam String home1</span><br><span class="line">[4] @RequestParam MultipartFile file</span><br><span class="line">[5] @PathVariable int id</span><br><span class="line">[6] @RequestHeader String header</span><br><span class="line">[7] @CookieValue String token</span><br><span class="line">[8] @Value String home2</span><br><span class="line">[9] HttpServletRequest request</span><br><span class="line">[10] @ModelAttribute User user1</span><br><span class="line">[11] User user2</span><br><span class="line">[12] @RequestBody User user3</span><br></pre></td></tr></table></figure>

<p>我们已经能够拿到<code>Controller</code>中<code>HandlerMethod</code>的属性，例如注解、参数类型、参数名称等</p>
<p><strong>RequestParamMethodArgumentResolver</strong></p>
<p>我们添加一个<code>RequestParamMethodArgumentResolver</code>来解析<code>@RequestParam</code>注解：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第一个参数为BeanFactory、第二个参数为省略@RequestParam是否解析属性</span></span><br><span class="line"><span class="type">RequestParamMethodArgumentResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RequestParamMethodArgumentResolver</span>(<span class="literal">null</span>, <span class="literal">false</span>);</span><br></pre></td></tr></table></figure>

<p>调用它的方法解析数据：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (resolver.supportsParameter(parameter)) &#123;</span><br><span class="line">    <span class="comment">// 支持此参数解析</span></span><br><span class="line">    <span class="type">Object</span> <span class="variable">v</span> <span class="operator">=</span> resolver.resolveArgument(parameter, container, <span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request), <span class="literal">null</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;[&quot;</span> + parameter.getParameterIndex() + <span class="string">&quot;] &quot;</span> + str + parameter.getParameterType().getSimpleName() + <span class="string">&quot; &quot;</span> + parameter.getParameterName() + <span class="string">&quot;-&gt;&quot;</span> + v);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;[&quot;</span> + parameter.getParameterIndex() + <span class="string">&quot;] &quot;</span> + str + parameter.getParameterType().getSimpleName() + <span class="string">&quot; &quot;</span> + parameter.getParameterName());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">[0] @RequestParam String name1-&gt;zhangsan</span><br><span class="line">[1] String name2</span><br><span class="line">[2] @RequestParam int age-&gt;18</span><br><span class="line">[3] @RequestParam String home1-&gt;$&#123;JAVA_HOME&#125;</span><br><span class="line">[4] @RequestParam MultipartFile file-&gt;org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@50313382</span><br><span class="line">[5] @PathVariable int id</span><br><span class="line">[6] @RequestHeader String header</span><br><span class="line">[7] @CookieValue String token</span><br><span class="line">[8] @Value String home2</span><br><span class="line">[9] HttpServletRequest request</span><br><span class="line">[10] @ModelAttribute User user1</span><br><span class="line">[11] User user2</span><br><span class="line">[12] @RequestBody User user3</span><br></pre></td></tr></table></figure>

<p>发现只要标注了<code>@RequestParam</code>注解的参数都已经被解析成功。</p>
<p>我们将解析出来的值的类型打印出来：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">System.out.println(v.getClass());</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">class java.lang.String</span><br><span class="line">[0] @RequestParam String name1-&gt;zhangsan</span><br><span class="line">[1] String name2</span><br><span class="line">class java.lang.String</span><br><span class="line">[2] @RequestParam int age-&gt;18</span><br><span class="line">class java.lang.String</span><br><span class="line">[3] @RequestParam String home1-&gt;$&#123;JAVA_HOME&#125;</span><br><span class="line">class org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile</span><br><span class="line">[4] @RequestParam MultipartFile file-&gt;org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@50313382</span><br><span class="line">[5] @PathVariable int id</span><br><span class="line">[6] @RequestHeader String header</span><br><span class="line">[7] @CookieValue String token</span><br><span class="line">[8] @Value String home2</span><br><span class="line">[9] HttpServletRequest request</span><br><span class="line">[10] @ModelAttribute User user1</span><br><span class="line">[11] User user2</span><br><span class="line">[12] @RequestBody User user3</span><br></pre></td></tr></table></figure>

<p>发现age参数没有进行类型转换，我们期望的是数值型，但最终还是字符串类型。</p>
<p>我们需要准备一个类型转换工厂：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ServletRequestDataBinderFactory</span> <span class="variable">factory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletRequestDataBinderFactory</span>(<span class="literal">null</span>, <span class="literal">null</span>);</span><br></pre></td></tr></table></figure>

<p>在调用<code>resolveArgument()</code>方法时传入此工厂：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Object</span> <span class="variable">v</span> <span class="operator">=</span> resolver.resolveArgument(parameter, container, <span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request), factory);</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">class java.lang.String</span><br><span class="line">[0] @RequestParam String name1-&gt;zhangsan</span><br><span class="line">[1] String name2</span><br><span class="line">class java.lang.Integer</span><br><span class="line">[2] @RequestParam int age-&gt;18</span><br><span class="line">class java.lang.String</span><br><span class="line">[3] @RequestParam String home1-&gt;$&#123;JAVA_HOME&#125;</span><br><span class="line">class org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile</span><br><span class="line">[4] @RequestParam MultipartFile file-&gt;org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@6c372fe6</span><br><span class="line">[5] @PathVariable int id</span><br><span class="line">[6] @RequestHeader String header</span><br><span class="line">[7] @CookieValue String token</span><br><span class="line">[8] @Value String home2</span><br><span class="line">[9] HttpServletRequest request</span><br><span class="line">[10] @ModelAttribute User user1</span><br><span class="line">[11] User user2</span><br><span class="line">[12] @RequestBody User user3</span><br></pre></td></tr></table></figure>

<p>发现age参数已经帮我们进行了类型转换。</p>
<p>再次观察输出结果，发现在request中我们没有传入home1参数，因此原样输出了<code>$&#123;JAVA_HOME&#125;</code>，但这并不是我们想要的结果，我们需要的是<code>$&#123;JAVA_HOME&#125;</code>被解析后的结果。说明表达式并没有被解析，我们可以使用<code>BeanFactory</code>容器提供的表达式解析器去解析。</p>
<p>获取<code>beanFactory</code>，在创建参数解析器的时候传入<code>BeanFactory</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">DefaultListableBeanFactory</span> <span class="variable">beanFactory</span> <span class="operator">=</span> context.getDefaultListableBeanFactory();</span><br><span class="line"><span class="type">RequestParamMethodArgumentResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RequestParamMethodArgumentResolver</span>(beanFactory, <span class="literal">false</span>);</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">class java.lang.String</span><br><span class="line">[0] @RequestParam String name1-&gt;zhangsan</span><br><span class="line">[1] String name2</span><br><span class="line">class java.lang.Integer</span><br><span class="line">[2] @RequestParam int age-&gt;18</span><br><span class="line">class java.lang.String</span><br><span class="line">[3] @RequestParam String home1-&gt;C:\Path\jdk-14.0.1</span><br><span class="line">class org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile</span><br><span class="line">[4] @RequestParam MultipartFile file-&gt;org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@19e7a160</span><br><span class="line">[5] @PathVariable int id</span><br><span class="line">[6] @RequestHeader String header</span><br><span class="line">[7] @CookieValue String token</span><br><span class="line">[8] @Value String home2</span><br><span class="line">[9] HttpServletRequest request</span><br><span class="line">[10] @ModelAttribute User user1</span><br><span class="line">[11] User user2</span><br><span class="line">[12] @RequestBody User user3</span><br></pre></td></tr></table></figure>

<p>发现<code>$&#123;&#125;</code>表达式成功被解析。</p>
<p>我们将<code>new RequestParamMethodArgumentResolver()</code>第二个参数设置为<code>true</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第一个参数为BeanFactory、第二个参数为省略@RequestParam是否解析属性</span></span><br><span class="line"><span class="type">RequestParamMethodArgumentResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RequestParamMethodArgumentResolver</span>(<span class="literal">null</span>, <span class="literal">true</span>);</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">class java.lang.String</span><br><span class="line">[0] @RequestParam String name1-&gt;zhangsan</span><br><span class="line">class java.lang.String</span><br><span class="line">[1] String name2-&gt;lisi</span><br><span class="line">class java.lang.Integer</span><br><span class="line">[2] @RequestParam int age-&gt;18</span><br><span class="line">class java.lang.String</span><br><span class="line">[3] @RequestParam String home1-&gt;C:\Path\jdk-14.0.1</span><br><span class="line">class org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile</span><br><span class="line">[4] @RequestParam MultipartFile file-&gt;org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@4e31276e</span><br><span class="line">Exception in thread &quot;main&quot; java.lang.IllegalStateException: Optional int parameter &#x27;id&#x27; is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type.</span><br><span class="line">        at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.handleNullValue(AbstractNamedValueMethodArgumentResolver.java:263)</span><br><span class="line">        at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:116)</span><br><span class="line">        at com.spring.test._21_.A21.main(A21.java:87)</span><br></pre></td></tr></table></figure>

<p>发现<code>name2</code>这种没有带<code>@RequestParam</code>注解的参数也被成功解析了。</p>
<p>但是解析到带有<code>@PathVariable</code>注解的参数<code>id</code>却出错了，这是因为只要没有带<code>@RequestParam</code>注解的参数例如<code>@PathVariable</code>、<code>@RequestHeader</code>、<code>@CookieValue</code>等注解<code>RequestParamMethodArgumentResolver</code>都会尝试去解析，因此会解析失败。针对这种情况，其实我们只需要把其他类型的解析器都添加上，每个解析器都会去尝试解析，只要有一个解析器解析成功就以此值为准。</p>
<p>Spring中提供了<code>HandlerMethodArgumentResolverComposite</code>类，它使用了组合模式。我们只需要将解析器添加进去，直接调用<code>HandlerMethodArgumentResolverComposite</code>的<code>supportsParameter()</code>方法和<code>resolveArgument()</code>方法即可，不需要一个一个去调用判断，体现了组合模式的优势。</p>
<p><code>HandlerMethodArgumentResolverComposite</code>使用示例：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">HandlerMethodArgumentResolverComposite</span> <span class="variable">composite</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethodArgumentResolverComposite</span>();</span><br><span class="line">composite.addResolvers(</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">RequestParamMethodArgumentResolver</span>(beanFactory, <span class="literal">false</span>)</span><br><span class="line">);</span><br><span class="line"><span class="comment">//...</span></span><br><span class="line"><span class="keyword">if</span> (composite.supportsParameter(parameter)) &#123;</span><br><span class="line">    <span class="comment">// 支持此参数解析</span></span><br><span class="line">    <span class="type">Object</span> <span class="variable">v</span> <span class="operator">=</span> composite.resolveArgument(parameter, container, <span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request), factory);</span><br><span class="line">    System.out.println(v.getClass());</span><br><span class="line">    System.out.println(<span class="string">&quot;[&quot;</span> + parameter.getParameterIndex() + <span class="string">&quot;] &quot;</span> + str + parameter.getParameterType().getSimpleName() + <span class="string">&quot; &quot;</span> + parameter.getParameterName() + <span class="string">&quot;-&gt;&quot;</span> + v);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;[&quot;</span> + parameter.getParameterIndex() + <span class="string">&quot;] &quot;</span> + str + parameter.getParameterType().getSimpleName() + <span class="string">&quot; &quot;</span> + parameter.getParameterName());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>PathVariableMethodArgumentResolver</strong></p>
<p><code>PathVariableMethodArgumentResolver</code>解析器实际上是从request去取<code>HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE</code>键对应的<code>map</code>值，然后从这个<code>map</code>中去取值，这里我们没有<code>HandlerMapping</code>去解析映射，因此我们手动模拟：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Map&lt;String, String&gt; map = <span class="keyword">new</span> <span class="title class_">AntPathMatcher</span>().extractUriTemplateVariables(<span class="string">&quot;/test/&#123;id&#125;&quot;</span>, <span class="string">&quot;/test/123&quot;</span>);</span><br><span class="line">request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);</span><br></pre></td></tr></table></figure>

<p><code>map</code>的内容实际为：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;id=123&#125;</span><br></pre></td></tr></table></figure>

<p>我们添加<code>PathVariableMethodArgumentResolver</code>解析器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">composite.addResolvers(</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">PathVariableMethodArgumentResolver</span>()</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[5] @PathVariable int id-&gt;123</span><br></pre></td></tr></table></figure>

<p>发现<code>id</code>参数已经成功被解析。</p>
<p><strong>RequestHeaderMethodArgumentResolver</strong></p>
<p><code>RequestHeaderMethodArgumentResolver</code>解析器主要解析请求头中的内容，我们添加此解析器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">composite.addResolvers(</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">RequestHeaderMethodArgumentResolver</span>(beanFactory)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[6] @RequestHeader String header-&gt;application/json</span><br></pre></td></tr></table></figure>

<p>发现<code>header</code>参数已经成功被解析。</p>
<p><strong>ServletCookieValueMethodArgumentResolver</strong></p>
<p><code>ServletCookieValueMethodArgumentResolver</code>解析器是从<code>cookie</code>中获取数据，我们添加此解析器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">composite.addResolvers(</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ServletCookieValueMethodArgumentResolver</span>(beanFactory)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[7] @CookieValue String token-&gt;123456</span><br></pre></td></tr></table></figure>

<p>发现<code>token</code>参数已经成功被解析。</p>
<p><strong>ExpressionValueMethodArgumentResolver</strong></p>
<p><code>ExpressionValueMethodArgumentResolver</code>解析器从<code>BeanFactory</code>中获取数据，并且支持EL表达式，我们添加此解析器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">composite.addResolvers(</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ExpressionValueMethodArgumentResolver</span>(beanFactory)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[8] @Value String home2-&gt;C:\Path\jdk-14.0.1</span><br></pre></td></tr></table></figure>

<p>发现<code>home2</code>参数已经成功被解析。</p>
<p><strong>ServletRequestMethodArgumentResolver</strong></p>
<p><code>ServletRequestMethodArgumentResolver</code>解析器是根据类型解析数据，会将web容器中的类型注入，我们添加此解析器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">composite.addResolvers(</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ServletRequestMethodArgumentResolver</span>()</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">HttpServletRequest request-&gt;org.springframework.web.multipart.support.StandardMultipartHttpServletRequest@6c0d7c83</span><br></pre></td></tr></table></figure>

<p>发现<code>request</code>参数已经成功被解析。</p>
<p>除了解析<code>HttpServletRequest</code>类型之外，它还支持很多类型，我们可以查看它的<code>supportsParameter()</code>方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">supportsParameter</span><span class="params">(MethodParameter parameter)</span> &#123;</span><br><span class="line">    Class&lt;?&gt; paramType = parameter.getParameterType();</span><br><span class="line">    <span class="keyword">return</span> WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType) || HttpSession.class.isAssignableFrom(paramType) || pushBuilder != <span class="literal">null</span> &amp;&amp; pushBuilder.isAssignableFrom(paramType) || Principal.class.isAssignableFrom(paramType) &amp;&amp; !parameter.hasParameterAnnotations() || InputStream.class.isAssignableFrom(paramType) || Reader.class.isAssignableFrom(paramType) || HttpMethod.class == paramType || Locale.class == paramType || TimeZone.class == paramType || ZoneId.class == paramType;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>可以发现一些常用的类型，例如<code>ServletRequest</code>、<code>HttpSession</code>等</p>
<p><strong>ServletModelAttributeMethodProcessor</strong></p>
<p><code>ServletModelAttributeMethodProcessor</code>解析器可以解析请求参数并封装为<code>Model</code>，最终会存到<code>ModelAndViewContainer</code>中，我们添加此解析器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">composite.addResolvers(</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">        <span class="comment">// 参数为false代表必须有 @ModelAttribute注解</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">false</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[10] @ModelAttribute User user1-&gt;User&#123;name=&#x27;zhangsan&#x27;, age=18&#125;</span><br></pre></td></tr></table></figure>

<p>发现请求参数已经成功被封装为<code>User</code>对象。</p>
<p>我们可以再加一个<code>ServletModelAttributeMethodProcessor</code>参数为<code>true</code>的解析器，用来解析不带<code>@ModelAttribute</code>注解的参数：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">composite.addResolvers(</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">        <span class="comment">// 参数为true代表没有@ModelAttribute注解</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">true</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[11] User user2-&gt;User&#123;name=&#x27;zhangsan&#x27;, age=18&#125;</span><br></pre></td></tr></table></figure>

<p>发现不带<code>@ModelAttribute</code>注解的参数也已经成功被封装为<code>User</code>对象。</p>
<p>除了封装将请求参数封装为<code>Model</code>，还会存到<code>ModelAndViewContainer</code>中，我们打印<code>ModelAndViewContainer</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">System.out.println(<span class="string">&quot;model:&quot;</span> + container.getModel());</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[10] @ModelAttribute User user1-&gt;User&#123;name=&#x27;zhangsan&#x27;, age=18&#125;</span><br><span class="line">model:&#123;abc=User&#123;name=&#x27;zhangsan&#x27;, age=18&#125;, org.springframework.validation.BindingResult.abc=org.springframework.validation.BeanPropertyBindingResult: 0 errors&#125;</span><br><span class="line">class com.spring.test._21_.A21$User</span><br><span class="line">[11] User user2-&gt;User&#123;name=&#x27;zhangsan&#x27;, age=18&#125;</span><br><span class="line">model:&#123;abc=User&#123;name=&#x27;zhangsan&#x27;, age=18&#125;, org.springframework.validation.BindingResult.abc=org.springframework.validation.BeanPropertyBindingResult: 0 errors, user=User&#123;name=&#x27;zhangsan&#x27;, age=18&#125;, org.springframework.validation.BindingResult.user=org.springframework.validation.BeanPropertyBindingResult: 0 errors&#125;</span><br></pre></td></tr></table></figure>

<p>[10]是带有<code>@ModelAttribute</code>注解的参数，[11]是不带有<code>@ModelAttribute</code>注解的参数，发现都会将解析后的数据存到<code>ModelAndViewContainer</code>中。</p>
<p><strong>RequestResponseBodyMethodProcessor</strong></p>
<p><code>RequestResponseBodyMethodProcessor</code>解析器会将请求体中的数据绑定到对象上，我们添加此解析器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">composite.addResolvers(</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">false</span>),</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">RequestResponseBodyMethodProcessor</span>(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>())),</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">true</span>),</span><br><span class="line">        );</span><br></pre></td></tr></table></figure>

<p>注意<code>RequestResponseBodyMethodProcessor</code>解析器不能放在<code>ServletModelAttributeMethodProcessor(true)</code>之后，否则解析的时候会优先使用<code>ServletModelAttributeMethodProcessor(true)</code>解析器(从请求参数中取值)，就不会使用<code>RequestResponseBodyMethodProcessor</code>解析器。</p>
<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[12] @RequestBody User user3-&gt;User&#123;name=&#x27;lisi&#x27;, age=20&#125;</span><br></pre></td></tr></table></figure>

<p>发现带<code>@RequestBody</code>注解的参数已经成功被封装为<code>User</code>对象。</p>
<h1 id="参数名解析"><a href="#参数名解析" class="headerlink" title="参数名解析"></a>参数名解析</h1><h2 id="参数名称原理"><a href="#参数名称原理" class="headerlink" title="参数名称原理"></a>参数名称原理</h2><p>在通过<code>HandlerMethod</code>的<code>getMethodParameters()</code>方法获取<code>MethodParameter</code>之后，我们直接通过</p>
<p><code>MethodParameter</code>的<code>getParameterName()</code>方法获取参数名是获取不到的。我们必须通过<code>MethodParameter</code>的<code>initParameterNameDiscovery()</code>方法添加一个参数解析器才能获取到参数，说明参数的获取并不是我们想象的那么简单。</p>
<p>为了避免idea为我们编译类，我们在show模块下创建a22目录，并在a22目录下创建com.itheima.a22包，在此包下创建测试类。</p>
<p>我们创建一个测试类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Bean2</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Bean2</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">foo</span><span class="params">(String name, <span class="type">int</span> age)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>使用<code>javac</code>命令手动编译此类：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javac .\Bean2.java</span><br></pre></td></tr></table></figure>

<p>使用idea查看反编译后的代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Bean2</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Bean2</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">foo</span><span class="params">(String var1, <span class="type">int</span> var2)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>发现我们的参数名被丢弃了，变成了var1、var2。说明在不加任何编译参数的情况下，是不会保留代码参数名的。</p>
<p>使用javap命令反编译代码：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line">javap -c -v .\Bean2.class</span><br><span class="line">Classfile /F:/Java/黑马全套java教程/第2阶段企业级开发—基础框架/7、spring高级45讲/代码/代码/show/a22/com/itheima/a22/Bean2.class</span><br><span class="line">  Last modified 2023年4月4日; size 317 bytes</span><br><span class="line">  SHA-256 checksum 178e5acc8063e10d20b3da678a5d96fa1b8329c21794834b5304135963070a9a</span><br><span class="line">  Compiled from &quot;Bean2.java&quot;                                      </span><br><span class="line">public class com.itheima.a22.Bean2                                </span><br><span class="line">  minor version: 0                                                </span><br><span class="line">  major version: 58                                               </span><br><span class="line">  flags: (0x0021) ACC_PUBLIC, ACC_SUPER                           </span><br><span class="line">  this_class: #7                          // com/itheima/a22/Bean2</span><br><span class="line">  super_class: #2                         // java/lang/Object     </span><br><span class="line">  interfaces: 0, fields: 0, methods: 2, attributes: 1</span><br><span class="line">Constant pool:</span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">1 = Methodref          <span class="comment">#2.#3          // java/lang/Object.&quot;&lt;init&gt;&quot;:()V</span></span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">2 = Class              <span class="comment">#4             // java/lang/Object</span></span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">3 = NameAndType        <span class="comment">#5:#6          // &quot;&lt;init&gt;&quot;:()V</span></span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">4 = Utf8               java/lang/Object</span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">5 = Utf8               &lt;init&gt;</span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">6 = Utf8               ()V</span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">7 = Class              <span class="comment">#8             // com/itheima/a22/Bean2</span></span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">8 = Utf8               com/itheima/a22/Bean2</span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">9 = Utf8               Code</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">10 = Utf8               LineNumberTable</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">11 = Utf8               foo</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">12 = Utf8               (Ljava/lang/String;I)V</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">13 = Utf8               MethodParameters</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">14 = Utf8               name</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">15 = Utf8               age</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">16 = Utf8               SourceFile</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">17 = Utf8               Bean2.java</span></span><br><span class="line">&#123;</span><br><span class="line">  public com.itheima.a22.Bean2();</span><br><span class="line">    descriptor: ()V</span><br><span class="line">    flags: (0x0001) ACC_PUBLIC</span><br><span class="line">    Code:</span><br><span class="line">      stack=1, locals=1, args_size=1</span><br><span class="line">         0: aload_0</span><br><span class="line">         1: invokespecial #1                  // Method java/lang/Object.&quot;&lt;init&gt;&quot;:()V</span><br><span class="line">         4: return</span><br><span class="line">      LineNumberTable:</span><br><span class="line">        line 3: 0</span><br><span class="line"></span><br><span class="line">  public void foo(java.lang.String, int);</span><br><span class="line">    descriptor: (Ljava/lang/String;I)V</span><br><span class="line">    flags: (0x0001) ACC_PUBLIC</span><br><span class="line">    Code:</span><br><span class="line">      stack=0, locals=3, args_size=3</span><br><span class="line">         0: return</span><br><span class="line">      LineNumberTable:</span><br><span class="line">        line 6: 0</span><br><span class="line">    MethodParameters:</span><br><span class="line">      Name                           Flags</span><br><span class="line">      name</span><br><span class="line">      age</span><br><span class="line">&#125;</span><br><span class="line">SourceFile: &quot;Bean2.java&quot;</span><br></pre></td></tr></table></figure>

<p>发现并没有保存参数名称信息。</p>
<p>有两种方式可以解决这个问题：</p>
<ol>
<li>编译命令添加-parameters参数</li>
</ol>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javac -parameters .\Bean2.java</span><br></pre></td></tr></table></figure>

<p>然后再使用idea查看反编译后的代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Bean2</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Bean2</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">foo</span><span class="params">(String name, <span class="type">int</span> age)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>发现保留了参数名称。</p>
<p>使用javap命令反编译代码：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line">javap -c -v .\Bean2.class</span><br><span class="line">Classfile /F:/Java/黑马全套java教程/第2阶段企业级开发—基础框架/7、spring高级45讲/代码/代码/show/a22/com/itheima/a22/Bean2.class</span><br><span class="line">  Last modified 2023年4月4日; size 317 bytes</span><br><span class="line">  SHA-256 checksum 178e5acc8063e10d20b3da678a5d96fa1b8329c21794834b5304135963070a9a</span><br><span class="line">  Compiled from &quot;Bean2.java&quot;</span><br><span class="line">public class com.itheima.a22.Bean2</span><br><span class="line">  minor version: 0</span><br><span class="line">  major version: 58</span><br><span class="line">  flags: (0x0021) ACC_PUBLIC, ACC_SUPER</span><br><span class="line">  this_class: #7                          // com/itheima/a22/Bean2</span><br><span class="line">  super_class: #2                         // java/lang/Object</span><br><span class="line">  interfaces: 0, fields: 0, methods: 2, attributes: 1</span><br><span class="line">Constant pool:</span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">1 = Methodref          <span class="comment">#2.#3          // java/lang/Object.&quot;&lt;init&gt;&quot;:()V</span></span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">2 = Class              <span class="comment">#4             // java/lang/Object</span></span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">3 = NameAndType        <span class="comment">#5:#6          // &quot;&lt;init&gt;&quot;:()V</span></span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">4 = Utf8               java/lang/Object</span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">5 = Utf8               &lt;init&gt;</span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">6 = Utf8               ()V</span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">7 = Class              <span class="comment">#8             // com/itheima/a22/Bean2</span></span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">8 = Utf8               com/itheima/a22/Bean2</span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">9 = Utf8               Code</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">10 = Utf8               LineNumberTable</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">11 = Utf8               foo</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">12 = Utf8               (Ljava/lang/String;I)V</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">13 = Utf8               MethodParameters</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">14 = Utf8               name</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">15 = Utf8               age</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">16 = Utf8               SourceFile</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">17 = Utf8               Bean2.java</span></span><br><span class="line">&#123;</span><br><span class="line">  public com.itheima.a22.Bean2();</span><br><span class="line">    descriptor: ()V</span><br><span class="line">    flags: (0x0001) ACC_PUBLIC</span><br><span class="line">    Code:</span><br><span class="line">      stack=1, locals=1, args_size=1</span><br><span class="line">         0: aload_0</span><br><span class="line">         1: invokespecial #1                  // Method java/lang/Object.&quot;&lt;init&gt;&quot;:()V</span><br><span class="line">         4: return</span><br><span class="line">      LineNumberTable:</span><br><span class="line">        line 3: 0</span><br><span class="line"></span><br><span class="line">  public void foo(java.lang.String, int);</span><br><span class="line">    descriptor: (Ljava/lang/String;I)V</span><br><span class="line">    flags: (0x0001) ACC_PUBLIC</span><br><span class="line">    Code:</span><br><span class="line">      stack=0, locals=3, args_size=3</span><br><span class="line">         0: return</span><br><span class="line">      LineNumberTable:</span><br><span class="line">        line 6: 0</span><br><span class="line">    MethodParameters:</span><br><span class="line">      Name                           Flags</span><br><span class="line">      name</span><br><span class="line">      age</span><br><span class="line">&#125;</span><br><span class="line">SourceFile: &quot;Bean2.java&quot;</span><br></pre></td></tr></table></figure>

<p>发现比没有添加任何参数的编译命令多了<code>MethodParameters</code>值，其中记录了参数名称。</p>
<ol start="2">
<li>编译命令添加-g选项</li>
</ol>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javac -g .\Bean2.java</span><br></pre></td></tr></table></figure>

<p>然后再使用idea查看反编译后的代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Bean2</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Bean2</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">foo</span><span class="params">(String name, <span class="type">int</span> age)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>发现也保留了参数名称。</p>
<p>使用javap命令反编译代码：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line">javap -c -v .\Bean2.class</span><br><span class="line">Classfile /F:/Java/黑马全套java教程/第2阶段企业级开发—基础框架/7、spring高级45讲/代码/代码/show/a22/com/itheima/a22/Bean2.class</span><br><span class="line">  Last modified 2023年4月4日; size 418 bytes</span><br><span class="line">  SHA-256 checksum 7ca753d2e057ff492011420a6e1ec419c0025fbedff47832ddb179560b1b42cd</span><br><span class="line">  Compiled from &quot;Bean2.java&quot;                                                       </span><br><span class="line">public class com.itheima.a22.Bean2                                                 </span><br><span class="line">  minor version: 0                                                                 </span><br><span class="line">  major version: 58                                                                </span><br><span class="line">  flags: (0x0021) ACC_PUBLIC, ACC_SUPER                                            </span><br><span class="line">  this_class: #7                          // com/itheima/a22/Bean2                 </span><br><span class="line">  super_class: #2                         // java/lang/Object                      </span><br><span class="line">  interfaces: 0, fields: 0, methods: 2, attributes: 1                              </span><br><span class="line">Constant pool:                                                            </span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">1 = Methodref          <span class="comment">#2.#3          // java/lang/Object.&quot;&lt;init&gt;&quot;:()V</span></span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">2 = Class              <span class="comment">#4             // java/lang/Object</span></span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">3 = NameAndType        <span class="comment">#5:#6          // &quot;&lt;init&gt;&quot;:()V</span></span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">4 = Utf8               java/lang/Object</span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">5 = Utf8               &lt;init&gt;</span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">6 = Utf8               ()V</span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">7 = Class              <span class="comment">#8             // com/itheima/a22/Bean2</span></span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">8 = Utf8               com/itheima/a22/Bean2</span></span><br><span class="line"><span class="meta prompt_">   #</span><span class="language-bash">9 = Utf8               Code</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">10 = Utf8               LineNumberTable</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">11 = Utf8               LocalVariableTable</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">12 = Utf8               this</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">13 = Utf8               Lcom/itheima/a22/Bean2;</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">14 = Utf8               foo</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">15 = Utf8               (Ljava/lang/String;I)V</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">16 = Utf8               name</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">17 = Utf8               Ljava/lang/String;</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">18 = Utf8               age</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">19 = Utf8               I</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">20 = Utf8               SourceFile</span></span><br><span class="line"><span class="meta prompt_">  #</span><span class="language-bash">21 = Utf8               Bean2.java</span></span><br><span class="line">&#123;</span><br><span class="line">  public com.itheima.a22.Bean2();</span><br><span class="line">    descriptor: ()V</span><br><span class="line">    flags: (0x0001) ACC_PUBLIC</span><br><span class="line">    Code:</span><br><span class="line">      stack=1, locals=1, args_size=1</span><br><span class="line">         0: aload_0</span><br><span class="line">         1: invokespecial #1                  // Method java/lang/Object.&quot;&lt;init&gt;&quot;:()V</span><br><span class="line">         4: return</span><br><span class="line">      LineNumberTable:</span><br><span class="line">        line 3: 0</span><br><span class="line">      LocalVariableTable:</span><br><span class="line">        Start  Length  Slot  Name   Signature</span><br><span class="line">            0       5     0  this   Lcom/itheima/a22/Bean2;</span><br><span class="line"></span><br><span class="line">  public void foo(java.lang.String, int);</span><br><span class="line">    descriptor: (Ljava/lang/String;I)V</span><br><span class="line">    flags: (0x0001) ACC_PUBLIC</span><br><span class="line">    Code:</span><br><span class="line">      stack=0, locals=3, args_size=3</span><br><span class="line">         0: return</span><br><span class="line">      LineNumberTable:</span><br><span class="line">        line 6: 0</span><br><span class="line">      LocalVariableTable:</span><br><span class="line">        Start  Length  Slot  Name   Signature</span><br><span class="line">            0       1     0  this   Lcom/itheima/a22/Bean2;</span><br><span class="line">            0       1     1  name   Ljava/lang/String;</span><br><span class="line">            0       1     2   age   I</span><br><span class="line">&#125;</span><br><span class="line">SourceFile: &quot;Bean2.java&quot;</span><br></pre></td></tr></table></figure>

<p>发现比没有添加任何参数的编译命令多了<code>LocalVariableTable</code>值，其中记录了参数名称。</p>
<p>使用<code>-parameters</code>与使用<code>-g</code>选项的区别：</p>
<ul>
<li>使用<code>-parameters</code>生成的<code>MethodParameters</code>可以通过反射获取</li>
<li>使用<code>-g</code>生成的<code>LocalVariableTable</code>不能通过反射获取，但能通过ASM方式获取</li>
</ul>
<blockquote>
<p>Spring对这两种方式都支持</p>
</blockquote>
<h2 id="获取参数名称"><a href="#获取参数名称" class="headerlink" title="获取参数名称"></a>获取参数名称</h2><p>首先我们需要将<code>a22/com/itheima/a22</code>加入模块依赖：</p>
<ol>
<li>打开项目结构</li>
<li>点击模块，选择show模块</li>
<li>点击依赖，点击+号，选择JAR或目录</li>
<li>选择<code>a22/com/itheima/a22</code>，点击应用</li>
</ol>
<p>编写测试代码，分别通过反射和ASM的方式获取参数名：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A22</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> NoSuchMethodException, ClassNotFoundException &#123;</span><br><span class="line">        <span class="comment">// 1. 反射获取参数名</span></span><br><span class="line">        <span class="type">Method</span> <span class="variable">foo</span> <span class="operator">=</span> Bean2.class.getMethod(<span class="string">&quot;foo&quot;</span>, String.class, <span class="type">int</span>.class);</span><br><span class="line">        <span class="keyword">for</span> (Parameter parameter : foo.getParameters()) &#123;</span><br><span class="line">            System.out.println(parameter.getName());</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;-----------&quot;</span>);</span><br><span class="line">        <span class="comment">// 2. 基于 LocalVariableTable 本地变量表  spring中ParameterNameDiscoverer接口的实现</span></span><br><span class="line">        <span class="type">LocalVariableTableParameterNameDiscoverer</span> <span class="variable">discoverer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LocalVariableTableParameterNameDiscoverer</span>();</span><br><span class="line">        String[] parameterNames = discoverer.getParameterNames(foo);</span><br><span class="line">        System.out.println(Arrays.toString(parameterNames));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>测试不带任何参数编译类文件：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javac .\Bean2.java</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">arg0</span><br><span class="line">arg1</span><br><span class="line">-----------</span><br><span class="line">null</span><br></pre></td></tr></table></figure>

<p>发现通过反射获取的参数名为编译后的默认名称，而通过<code>LocalVariableTable</code>变量表的方式获取不到任何信息。</p>
<p>测试带<code>-parameters</code>参数编译类文件：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javac -parameters .\Bean2.java</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">name</span><br><span class="line">age</span><br><span class="line">-----------</span><br><span class="line">null</span><br></pre></td></tr></table></figure>

<p>发现通过反射能成功获取参数名称，而通过<code>LocalVariableTable</code>变量表的方式获取不到任何信息。</p>
<p>测试带<code>-g</code>选项编译类文件：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javac -g .\Bean2.java </span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">arg0</span><br><span class="line">arg1</span><br><span class="line">-----------</span><br><span class="line">[name, age]</span><br></pre></td></tr></table></figure>

<p>发现通过反射获取的参数名为编译后的默认名称，而通过<code>LocalVariableTable</code>变量表的方式能正常获取参数名称。</p>
<p><code>Spring</code>中其实将以上两种参数解析的方式统一了，<code>ParameterNameDiscoverer</code>接口有一个实现<code>DefaultParameterNameDiscoverer</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DefaultParameterNameDiscoverer</span> <span class="keyword">extends</span> <span class="title class_">PrioritizedParameterNameDiscoverer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">DefaultParameterNameDiscoverer</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (KotlinDetector.isKotlinReflectPresent() &amp;&amp; !NativeDetector.inNativeImage()) &#123;</span><br><span class="line">            <span class="built_in">this</span>.addDiscoverer(<span class="keyword">new</span> <span class="title class_">KotlinReflectionParameterNameDiscoverer</span>());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 通过反射</span></span><br><span class="line">        <span class="built_in">this</span>.addDiscoverer(<span class="keyword">new</span> <span class="title class_">StandardReflectionParameterNameDiscoverer</span>());</span><br><span class="line">        <span class="comment">// 通过本地变量表</span></span><br><span class="line">        <span class="built_in">this</span>.addDiscoverer(<span class="keyword">new</span> <span class="title class_">LocalVariableTableParameterNameDiscoverer</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>通过本地变量表的方式获取参数名称有一个局限性，它只能获取普通类的方法上的参数，而不能获取接口的方法上的参数名称。</p>
<p>我们创建一个接口：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Bean1</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">foo</span><span class="params">(String name, <span class="type">int</span> age)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>使用<code>javac -g .\Bean1.java</code>命令编译，然后使用<code>javap -c -v .\Bean1.class</code>命令反编译：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">Classfile /F:/Java/黑马全套java教程/第2阶段企业级开发—基础框架/7、spring高级45讲/代码/代码/show/a22/com/itheima/a22/Bean1.class</span><br><span class="line">  Last modified 2023年4月4日; size 146 bytes</span><br><span class="line">  SHA-256 checksum 916828ed8e8f37b1a074cb49b21e55a5b35ffd2ecad4926d0608828fc3d1bc5d</span><br><span class="line">  Compiled from &quot;Bean1.java&quot;</span><br><span class="line">public interface com.itheima.a22.Bean1</span><br><span class="line">  minor version: 0</span><br><span class="line">  major version: 58</span><br><span class="line">  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT</span><br><span class="line">  this_class: #1                          // com/itheima/a22/Bean1</span><br><span class="line">  super_class: #3                         // java/lang/Object</span><br><span class="line">  #6 = Utf8               (Ljava/lang/String;I)V</span><br><span class="line">  #7 = Utf8               SourceFile</span><br><span class="line">  #8 = Utf8               Bean1.java</span><br><span class="line">&#123;</span><br><span class="line">  public abstract void foo(java.lang.String, int);</span><br><span class="line">    descriptor: (Ljava/lang/String;I)V</span><br><span class="line">    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT</span><br><span class="line">&#125;</span><br><span class="line">SourceFile: &quot;Bean1.java&quot;</span><br></pre></td></tr></table></figure>

<p>发现反编译后的代码没有<code>LocalVariableTable</code>信息。</p>
<p>而通过<code>javac -parameters .\Bean2.java</code>这种方式对于普通类和接口都有效，我们可以测试一下，使用<code>javac -parameters  .\Bean1.java</code>命令编译，然后使用<code>javap -c -v .\Bean1.class</code>命令反编译：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">Classfile /F:/Java/黑马全套java教程/第2阶段企业级开发—基础框架/7、spring高级45讲/代码/代码/show/a22/com/itheima/a22/Bean1.class</span><br><span class="line">  Last modified 2023年4月4日; size 193 bytes</span><br><span class="line">  SHA-256 checksum 4c2d4e73c785737a30dd1c6986385adced085c4e191a26181f84d6fd253a8c7b</span><br><span class="line">  Compiled from &quot;Bean1.java&quot;                                                       </span><br><span class="line">public interface com.itheima.a22.Bean1                                             </span><br><span class="line">  minor version: 0                                                                 </span><br><span class="line">  major version: 58                                                                </span><br><span class="line">  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT                          </span><br><span class="line">  this_class: #1                          // com/itheima/a22/Bean1                 </span><br><span class="line">  super_class: #3                         // java/lang/Object                      </span><br><span class="line">  interfaces: 0, fields: 0, methods: 1, attributes: 1</span><br><span class="line">Constant pool:</span><br><span class="line">   #1 = Class              #2             // com/itheima/a22/Bean1</span><br><span class="line">   #2 = Utf8               com/itheima/a22/Bean1</span><br><span class="line">   #3 = Class              #4             // java/lang/Object</span><br><span class="line">   #4 = Utf8               java/lang/Object</span><br><span class="line">   #5 = Utf8               foo</span><br><span class="line">   #6 = Utf8               (Ljava/lang/String;I)V</span><br><span class="line">   #7 = Utf8               MethodParameters</span><br><span class="line">   #8 = Utf8               name</span><br><span class="line">   #9 = Utf8               age</span><br><span class="line">  #10 = Utf8               SourceFile</span><br><span class="line">  #11 = Utf8               Bean1.java</span><br><span class="line">&#123;</span><br><span class="line">  public abstract void foo(java.lang.String, int);</span><br><span class="line">    descriptor: (Ljava/lang/String;I)V</span><br><span class="line">    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT</span><br><span class="line">    MethodParameters:</span><br><span class="line">      Name                           Flags</span><br><span class="line">      name</span><br><span class="line">      age</span><br><span class="line">&#125;</span><br><span class="line">SourceFile: &quot;Bean1.java&quot;</span><br></pre></td></tr></table></figure>

<p>发现也有<code>MethodParameters</code>信息。</p>
<h1 id="对象绑定与类型转换"><a href="#对象绑定与类型转换" class="headerlink" title="对象绑定与类型转换"></a>对象绑定与类型转换</h1><h2 id="底层类型转换接口"><a href="#底层类型转换接口" class="headerlink" title="底层类型转换接口"></a>底层类型转换接口</h2><ol>
<li>底层第一套转换接口与实现：</li>
</ol>
<p><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/2.png" alt="img"></p>
<ul>
<li><code>Printer</code>把其它类型转为<code>String</code></li>
<li><code>Parser</code>把<code>String</code>转为其它类型</li>
<li><code>Formatter</code>综合<code>Printer</code>与<code>Parser</code>功能</li>
<li><code>Converter</code>把类型S转为类型T</li>
<li><code>Printer</code>、<code>Parser</code>、<code>Converter </code>经过适配转换成<code>GenericConverter</code>放入<code>Converters</code>集合</li>
<li><code>FormattingConversionService</code>利用其它们实现转换</li>
</ul>
<blockquote>
<p>第一套转换接口与实现是由Spring提供的</p>
</blockquote>
<ol start="2">
<li>底层第二套转换接口与实现：</li>
</ol>
<p><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/3.png" alt="img"></p>
<ul>
<li><code>PropertyEditor</code>把<code>String</code>与其它类型相互转换</li>
<li><code>PropertyEditorRegistry</code>可以注册多个<code>PropertyEditor</code>对象</li>
<li>与第一套接口直接可以通过<code>FormatterPropertyEditorAdapter</code>来进行适配</li>
</ul>
<blockquote>
<p>第二套转换接口与实现是由JDK提供的</p>
</blockquote>
<p>为什么要使用两套转换接口呢，这里可能是<code>Spring</code>的历史遗留问题，最早的时候<code>Spring</code>使用的是由<code>JDK</code>提供的类型转换接口，后来可能觉得<code>JDK</code>提供的功能不够全面，自己实现了一套类型转换接口。而为了保证版本向前兼容，因此保留了<code>JDK</code>提供的类型转换接口的实现。</p>
<h2 id="高级类型转换接口"><a href="#高级类型转换接口" class="headerlink" title="高级类型转换接口"></a>高级类型转换接口</h2><p><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/4.png" alt="img"></p>
<ul>
<li>它们都实现了<code>TypeConverter</code>这个高层转换接口，在转换时，会用到<code>TypeConverterDelegate</code>委派<code>ConversionService</code>与<code>PropertyEditorRegistry</code>真正执行转换（<code>Facade</code>门面模式）<ul>
<li>首先看是否有自定义转换器, <code>@InitBinder</code>添加的即属于这种 (用了适配器模式把<code>Formatter</code>转为需要的 <code>PropertyEditor</code>)</li>
<li>再看有没有<code>ConversionService</code>转换</li>
<li>再利用默认的<code>PropertyEditor</code>转换</li>
<li>最后有一些特殊处理</li>
</ul>
</li>
<li><code>SimpleTypeConverter</code>仅做类型转换</li>
<li><code>BeanWrapperImpl</code>为<code>bean</code>的属性赋值，当需要时做类型转换，走<code>Property</code></li>
<li><code>DirectFieldAccessor</code>为<code>bean</code>的属性赋值，当需要时做类型转换，走<code>Field</code></li>
<li><code>ServletRequestDataBinder</code>为<code>bean</code>的属性执行绑定，当需要时做类型转换，根据<code>directFieldAccess</code>选择走<code>Property</code>还是<code>Field</code>，具备校验与获取校验结果功能</li>
</ul>
<h2 id="类型转换与数据绑定示例"><a href="#类型转换与数据绑定示例" class="headerlink" title="类型转换与数据绑定示例"></a>类型转换与数据绑定示例</h2><h3 id="SimpleTypeConverter"><a href="#SimpleTypeConverter" class="headerlink" title="SimpleTypeConverter"></a>SimpleTypeConverter</h3><p><code>SimpleTypeConverter</code>仅有类型转换的功能：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestSimpleConverter</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">SimpleTypeConverter</span> <span class="variable">typeConverter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleTypeConverter</span>();</span><br><span class="line">        <span class="type">Integer</span> <span class="variable">number</span> <span class="operator">=</span> typeConverter.convertIfNecessary(<span class="string">&quot;13&quot;</span>, <span class="type">int</span>.class);</span><br><span class="line">        <span class="type">Date</span> <span class="variable">date</span> <span class="operator">=</span> typeConverter.convertIfNecessary(<span class="string">&quot;1999/03/04&quot;</span>, Date.class);</span><br><span class="line">        System.out.println(number);</span><br><span class="line">        System.out.println(date);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，控制台输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">13</span><br><span class="line">Thu Mar 04 00:00:00 CST 1999</span><br></pre></td></tr></table></figure>

<h3 id="BeanWrapperImpl"><a href="#BeanWrapperImpl" class="headerlink" title="BeanWrapperImpl"></a>BeanWrapperImpl</h3><p><code>BeanWrapperImpl</code>为<code>bean</code>的属性赋值，当需要时做类型转换，走<code>Property</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestBeanWrapper</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">MyBean</span> <span class="variable">target</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyBean</span>();</span><br><span class="line">        <span class="type">BeanWrapperImpl</span> <span class="variable">wrapper</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BeanWrapperImpl</span>(target);</span><br><span class="line">        wrapper.setPropertyValue(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;10&quot;</span>);</span><br><span class="line">        wrapper.setPropertyValue(<span class="string">&quot;b&quot;</span>, <span class="string">&quot;hello&quot;</span>);</span><br><span class="line">        wrapper.setPropertyValue(<span class="string">&quot;c&quot;</span>, <span class="string">&quot;1999/03/04&quot;</span>);</span><br><span class="line">        System.out.println(target);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyBean</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> a;</span><br><span class="line">        <span class="keyword">private</span> String b;</span><br><span class="line">        <span class="keyword">private</span> Date c;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getA</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> a;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setA</span><span class="params">(<span class="type">int</span> a)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.a = a;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getB</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> b;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setB</span><span class="params">(String b)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.b = b;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> Date <span class="title function_">getC</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> c;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setC</span><span class="params">(Date c)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.c = c;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;MyBean&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;a=&quot;</span> + a +</span><br><span class="line">                   <span class="string">&quot;, b=&#x27;&quot;</span> + b + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                   <span class="string">&quot;, c=&quot;</span> + c +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，控制台输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MyBean&#123;a=10, b=&#x27;hello&#x27;, c=Thu Mar 04 00:00:00 CST 1999&#125;</span><br></pre></td></tr></table></figure>

<h3 id="DirectFieldAccessor"><a href="#DirectFieldAccessor" class="headerlink" title="DirectFieldAccessor"></a>DirectFieldAccessor</h3><p><code>DirectFieldAccessor</code>为<code>bean</code>的属性赋值，当需要时做类型转换，走<code>Field</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestFieldAccessor</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 利用反射原理, 为 bean 的属性赋值</span></span><br><span class="line">        <span class="type">MyBean</span> <span class="variable">target</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyBean</span>();</span><br><span class="line">        <span class="type">DirectFieldAccessor</span> <span class="variable">accessor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DirectFieldAccessor</span>(target);</span><br><span class="line">        accessor.setPropertyValue(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;10&quot;</span>);</span><br><span class="line">        accessor.setPropertyValue(<span class="string">&quot;b&quot;</span>, <span class="string">&quot;hello&quot;</span>);</span><br><span class="line">        accessor.setPropertyValue(<span class="string">&quot;c&quot;</span>, <span class="string">&quot;1999/03/04&quot;</span>);</span><br><span class="line">        System.out.println(target);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyBean</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> a;</span><br><span class="line">        <span class="keyword">private</span> String b;</span><br><span class="line">        <span class="keyword">private</span> Date c;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;MyBean&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;a=&quot;</span> + a +</span><br><span class="line">                   <span class="string">&quot;, b=&#x27;&quot;</span> + b + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                   <span class="string">&quot;, c=&quot;</span> + c +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，控制台输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MyBean&#123;a=10, b=&#x27;hello&#x27;, c=Thu Mar 04 00:00:00 CST 1999&#125;</span><br></pre></td></tr></table></figure>

<h3 id="DataBinder"><a href="#DataBinder" class="headerlink" title="DataBinder"></a>DataBinder</h3><p><code>DataBinder</code>执行数据绑定，<code>DataBinder</code>有两个方法</p>
<ul>
<li><code>initBeanPropertyAccess()</code>使用property的方式绑定数据</li>
<li><code>initDirectFieldAccess()</code>使用field的方式绑定数据</li>
</ul>
<p>我们查看源代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">initBeanPropertyAccess</span><span class="params">()</span> &#123;</span><br><span class="line">    Assert.state(<span class="built_in">this</span>.bindingResult == <span class="literal">null</span>, <span class="string">&quot;DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods&quot;</span>);</span><br><span class="line">    <span class="built_in">this</span>.directFieldAccess = <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">initDirectFieldAccess</span><span class="params">()</span> &#123;</span><br><span class="line">    Assert.state(<span class="built_in">this</span>.bindingResult == <span class="literal">null</span>, <span class="string">&quot;DataBinder is already initialized - call initDirectFieldAccess before other configuration methods&quot;</span>);</span><br><span class="line">    <span class="built_in">this</span>.directFieldAccess = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>发现就是将<code>directFieldAccess</code>修改为不同的值。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestDataBinder</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">MyBean</span> <span class="variable">target</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyBean</span>();</span><br><span class="line">        <span class="type">DataBinder</span> <span class="variable">dataBinder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DataBinder</span>(target);</span><br><span class="line">        dataBinder.initDirectFieldAccess();</span><br><span class="line">        <span class="type">MutablePropertyValues</span> <span class="variable">pvs</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MutablePropertyValues</span>();</span><br><span class="line">        pvs.add(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;10&quot;</span>);</span><br><span class="line">        pvs.add(<span class="string">&quot;b&quot;</span>, <span class="string">&quot;hello&quot;</span>);</span><br><span class="line">        pvs.add(<span class="string">&quot;c&quot;</span>, <span class="string">&quot;1999/03/04&quot;</span>);</span><br><span class="line">        dataBinder.bind(pvs);</span><br><span class="line">        System.out.println(target);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyBean</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> a;</span><br><span class="line">        <span class="keyword">private</span> String b;</span><br><span class="line">        <span class="keyword">private</span> Date c;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;MyBean&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;a=&quot;</span> + a +</span><br><span class="line">                   <span class="string">&quot;, b=&#x27;&quot;</span> + b + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                   <span class="string">&quot;, c=&quot;</span> + c +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，控制台输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MyBean&#123;a=10, b=&#x27;hello&#x27;, c=Thu Mar 04 00:00:00 CST 1999&#125;</span><br></pre></td></tr></table></figure>

<p>以上是普通环境中<code>DataBinder</code>的数据绑定，在<code>WEB</code>环境中也有相对应的实现，我们需要将<code>DataBinder</code>的实现替换为<code>ServletRequestDataBinder</code>，同时将<code>MutablePropertyValues</code>的实现替换为<code>ServletRequestParameterPropertyValues</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestServletDataBinder</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// web 环境下数据绑定</span></span><br><span class="line">        <span class="type">MyBean</span> <span class="variable">target</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyBean</span>();</span><br><span class="line">        <span class="type">ServletRequestDataBinder</span> <span class="variable">dataBinder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletRequestDataBinder</span>(target);</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        request.setParameter(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;10&quot;</span>);</span><br><span class="line">        request.setParameter(<span class="string">&quot;b&quot;</span>, <span class="string">&quot;hello&quot;</span>);</span><br><span class="line">        request.setParameter(<span class="string">&quot;c&quot;</span>, <span class="string">&quot;1999/03/04&quot;</span>);</span><br><span class="line"></span><br><span class="line">        dataBinder.bind(<span class="keyword">new</span> <span class="title class_">ServletRequestParameterPropertyValues</span>(request));</span><br><span class="line"></span><br><span class="line">        System.out.println(target);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyBean</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> a;</span><br><span class="line">        <span class="keyword">private</span> String b;</span><br><span class="line">        <span class="keyword">private</span> Date c;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getA</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> a;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setA</span><span class="params">(<span class="type">int</span> a)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.a = a;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getB</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> b;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setB</span><span class="params">(String b)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.b = b;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> Date <span class="title function_">getC</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> c;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setC</span><span class="params">(Date c)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.c = c;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;MyBean&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;a=&quot;</span> + a +</span><br><span class="line">                   <span class="string">&quot;, b=&#x27;&quot;</span> + b + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                   <span class="string">&quot;, c=&quot;</span> + c +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，控制台输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MyBean&#123;a=10, b=&#x27;hello&#x27;, c=Thu Mar 04 00:00:00 CST 1999&#125;</span><br></pre></td></tr></table></figure>

<p>实际上参数解析器<code>ModelAttributeMethodProcessor</code>使用的类型转换就是<code>ServletRequestDataBinder</code>。</p>
<h2 id="数据绑定工厂"><a href="#数据绑定工厂" class="headerlink" title="数据绑定工厂"></a>数据绑定工厂</h2><p>试想一种场景，如果传入的参数是自定义的格式，那么<code>DataBinder</code>还能正常工作吗？先看下面一个例子：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestServletDataBinderFactory</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        request.setParameter(<span class="string">&quot;birthday&quot;</span>, <span class="string">&quot;1999|01|02&quot;</span>);</span><br><span class="line">        request.setParameter(<span class="string">&quot;address.name&quot;</span>, <span class="string">&quot;西安&quot;</span>);</span><br><span class="line">        <span class="type">User</span> <span class="variable">target</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        <span class="type">ServletRequestDataBinder</span> <span class="variable">dataBinder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletRequestDataBinder</span>(target);</span><br><span class="line">        dataBinder.bind(<span class="keyword">new</span> <span class="title class_">ServletRequestParameterPropertyValues</span>(request));</span><br><span class="line">        System.out.println(target);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> Date birthday;</span><br><span class="line">        <span class="keyword">private</span> Address address;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> Address <span class="title function_">getAddress</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> address;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAddress</span><span class="params">(Address address)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.address = address;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> Date <span class="title function_">getBirthday</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> birthday;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBirthday</span><span class="params">(Date birthday)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.birthday = birthday;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;User&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;birthday=&quot;</span> + birthday +</span><br><span class="line">                   <span class="string">&quot;, address=&quot;</span> + address +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Address</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;Address&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;name=&#x27;&quot;</span> + name + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">User&#123;birthday=null, address=Address&#123;name=&#x27;西安&#x27;&#125;&#125;</span><br></pre></td></tr></table></figure>

<p>发现我们自定义的日期格式，<code>DataBinder</code>是不能识别的，这时候就需要用到类型转换工厂。有两种方式可以实现自定义格式绑定：</p>
<ul>
<li>使用底层第一套类型转换，用<code>@InitBinder</code>转换，即<code>PropertyEditorRegistry</code>+<code>PropertyEditor</code></li>
<li>使用底层第二套类型转换，用<code>ConversionService</code>转换<strong>，</strong>即<code>ConversionService</code>+<code>Formatter</code></li>
<li>使用默认的<code>ConversionService</code>进行转换</li>
</ul>
<ol>
<li>用<code>@InitBinder</code>转换：</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 创建自定义转换器</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyDateFormatter</span> <span class="keyword">implements</span> <span class="title class_">Formatter</span>&lt;Date&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(MyDateFormatter.class);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String desc;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MyDateFormatter</span><span class="params">(String desc)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.desc = desc;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">print</span><span class="params">(Date date, Locale locale)</span> &#123;</span><br><span class="line">        <span class="type">SimpleDateFormat</span> <span class="variable">sdf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">&quot;yyyy|MM|dd&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> sdf.format(date);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Date <span class="title function_">parse</span><span class="params">(String text, Locale locale)</span> <span class="keyword">throws</span> ParseException &#123;</span><br><span class="line">        log.debug(<span class="string">&quot;&gt;&gt;&gt;&gt;&gt;&gt; 进入了: &#123;&#125;&quot;</span>, desc);</span><br><span class="line">        <span class="type">SimpleDateFormat</span> <span class="variable">sdf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">&quot;yyyy|MM|dd&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> sdf.parse(text);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 创建Controller，使用<span class="doctag">@InitBinder</span>注解，传入WebDataBinder类型，并加入自定义的转换器</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyController</span> &#123;</span><br><span class="line">    <span class="meta">@InitBinder</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">aaa</span><span class="params">(WebDataBinder dataBinder)</span> &#123;</span><br><span class="line">        <span class="comment">// 扩展 dataBinder 的转换器</span></span><br><span class="line">        dataBinder.addCustomFormatter(<span class="keyword">new</span> <span class="title class_">MyDateFormatter</span>(<span class="string">&quot;用 @InitBinder 方式扩展的&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestServletDataBinderFactory</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        request.setParameter(<span class="string">&quot;birthday&quot;</span>, <span class="string">&quot;1999|01|02&quot;</span>);</span><br><span class="line">        request.setParameter(<span class="string">&quot;address.name&quot;</span>, <span class="string">&quot;西安&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">User</span> <span class="variable">target</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        <span class="comment">// &quot;2. 用 @InitBinder 转换&quot;          PropertyEditorRegistry PropertyEditor</span></span><br><span class="line">        <span class="type">InvocableHandlerMethod</span> <span class="variable">method</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InvocableHandlerMethod</span>(<span class="keyword">new</span> <span class="title class_">MyController</span>(), MyController.class.getMethod(<span class="string">&quot;aaa&quot;</span>, WebDataBinder.class));</span><br><span class="line">        <span class="comment">// 创建工厂</span></span><br><span class="line">        <span class="type">ServletRequestDataBinderFactory</span> <span class="variable">factory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletRequestDataBinderFactory</span>(List.of(method), <span class="literal">null</span>);</span><br><span class="line">        <span class="comment">// 创建WebDataBinder </span></span><br><span class="line">        <span class="type">WebDataBinder</span> <span class="variable">dataBinder</span> <span class="operator">=</span> factory.createBinder(<span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request), target, <span class="string">&quot;user&quot;</span>);</span><br><span class="line">        dataBinder.bind(<span class="keyword">new</span> <span class="title class_">ServletRequestParameterPropertyValues</span>(request));</span><br><span class="line">        System.out.println(target);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">       </span><br><span class="line">        <span class="keyword">private</span> Date birthday;</span><br><span class="line">        <span class="keyword">private</span> Address address;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> Address <span class="title function_">getAddress</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> address;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAddress</span><span class="params">(Address address)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.address = address;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> Date <span class="title function_">getBirthday</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> birthday;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBirthday</span><span class="params">(Date birthday)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.birthday = birthday;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;User&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;birthday=&quot;</span> + birthday +</span><br><span class="line">                   <span class="string">&quot;, address=&quot;</span> + address +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Address</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;Address&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;name=&#x27;&quot;</span> + name + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>使用<code>@InitBinder</code>转换的步骤：</p>
<ol>
<li>标注了<code>@InitBinder</code>注解的方法向<code>WebDataBinder</code>中添加了自定义的转换器</li>
<li>在创建<code>ServletRequestDataBinderFactory</code>时，添加了<code>InvocableHandlerMethod</code></li>
<li>在通过<code>factory.createBinder()</code>获取<code>WebDataBinder</code>时会回调标注了<code>@InitBinder</code>注解的方法，并添加自定义的转换器</li>
</ol>
<p>我们查看<code>WebDataBinder</code>的<code>addCustomFormatter()</code>方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addCustomFormatter</span><span class="params">(Formatter&lt;?&gt; formatter)</span> &#123;</span><br><span class="line">    <span class="type">FormatterPropertyEditorAdapter</span> <span class="variable">adapter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FormatterPropertyEditorAdapter</span>(formatter);</span><br><span class="line">    <span class="built_in">this</span>.getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>发现实际调用的是<code>PropertyEditorRegistry</code>的<code>registerCustomEditor()</code>方法注册转换器，本质上是利用<code>PropertyEditorRegistry</code>+<code>PropertyEditor</code>的方式进行数据绑定。</p>
<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[DEBUG] 12:55:41.361 [main] com.itheima.a23.MyDateFormatter     - &gt;&gt;&gt;&gt;&gt;&gt; 进入了: 用 @InitBinder 方式扩展的 </span><br><span class="line">User&#123;birthday=Sat Jan 02 00:00:00 CST 1999, address=Address&#123;name=&#x27;西安&#x27;&#125;&#125;</span><br></pre></td></tr></table></figure>

<p>发现数据成功被绑定。</p>
<ol start="2">
<li>用<code>ConversionService</code>转换：</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestServletDataBinderFactory</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        request.setParameter(<span class="string">&quot;birthday&quot;</span>, <span class="string">&quot;1999|01|02&quot;</span>);</span><br><span class="line">        request.setParameter(<span class="string">&quot;address.name&quot;</span>, <span class="string">&quot;西安&quot;</span>);</span><br><span class="line">        <span class="type">User</span> <span class="variable">target</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        <span class="type">FormattingConversionService</span> <span class="variable">service</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FormattingConversionService</span>();</span><br><span class="line">        service.addFormatter(<span class="keyword">new</span> <span class="title class_">MyDateFormatter</span>(<span class="string">&quot;用 ConversionService 方式扩展转换功能&quot;</span>));</span><br><span class="line">        <span class="type">ConfigurableWebBindingInitializer</span> <span class="variable">initializer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ConfigurableWebBindingInitializer</span>();</span><br><span class="line">        initializer.setConversionService(service);</span><br><span class="line">        <span class="type">ServletRequestDataBinderFactory</span> <span class="variable">factory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletRequestDataBinderFactory</span>(<span class="literal">null</span>, initializer);</span><br><span class="line">        <span class="type">WebDataBinder</span> <span class="variable">dataBinder</span> <span class="operator">=</span> factory.createBinder(<span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request), target, <span class="string">&quot;user&quot;</span>);</span><br><span class="line">        dataBinder.bind(<span class="keyword">new</span> <span class="title class_">ServletRequestParameterPropertyValues</span>(request));</span><br><span class="line">        System.out.println(target);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">private</span> Date birthday;</span><br><span class="line">        <span class="keyword">private</span> Address address;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> Address <span class="title function_">getAddress</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> address;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAddress</span><span class="params">(Address address)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.address = address;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> Date <span class="title function_">getBirthday</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> birthday;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBirthday</span><span class="params">(Date birthday)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.birthday = birthday;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;User&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;birthday=&quot;</span> + birthday +</span><br><span class="line">                   <span class="string">&quot;, address=&quot;</span> + address +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Address</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;Address&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;name=&#x27;&quot;</span> + name + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[DEBUG] 13:07:36.277 [main] com.itheima.a23.MyDateFormatter     - &gt;&gt;&gt;&gt;&gt;&gt; 进入了: 用 ConversionService 方式扩展转换功能 </span><br><span class="line">User&#123;birthday=Sat Jan 02 00:00:00 CST 1999, address=Address&#123;name=&#x27;西安&#x27;&#125;&#125;</span><br></pre></td></tr></table></figure>

<p>如果同时使用以上两种方式，那么会优先调用哪一种方式进行转换呢？</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestServletDataBinderFactory</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        request.setParameter(<span class="string">&quot;birthday&quot;</span>, <span class="string">&quot;1999|01|02&quot;</span>);</span><br><span class="line">        request.setParameter(<span class="string">&quot;address.name&quot;</span>, <span class="string">&quot;西安&quot;</span>);</span><br><span class="line">        <span class="type">User</span> <span class="variable">target</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        <span class="type">InvocableHandlerMethod</span> <span class="variable">method</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InvocableHandlerMethod</span>(<span class="keyword">new</span> <span class="title class_">MyController</span>(), MyController.class.getMethod(<span class="string">&quot;aaa&quot;</span>, WebDataBinder.class));</span><br><span class="line">        <span class="type">FormattingConversionService</span> <span class="variable">service</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FormattingConversionService</span>();</span><br><span class="line">        service.addFormatter(<span class="keyword">new</span> <span class="title class_">MyDateFormatter</span>(<span class="string">&quot;用 ConversionService 方式扩展转换功能&quot;</span>));</span><br><span class="line">        <span class="type">ConfigurableWebBindingInitializer</span> <span class="variable">initializer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ConfigurableWebBindingInitializer</span>();</span><br><span class="line">        initializer.setConversionService(service);</span><br><span class="line">        <span class="type">ServletRequestDataBinderFactory</span> <span class="variable">factory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletRequestDataBinderFactory</span>(List.of(method), initializer);</span><br><span class="line">        <span class="type">WebDataBinder</span> <span class="variable">dataBinder</span> <span class="operator">=</span> factory.createBinder(<span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request), target, <span class="string">&quot;user&quot;</span>);</span><br><span class="line">        dataBinder.bind(<span class="keyword">new</span> <span class="title class_">ServletRequestParameterPropertyValues</span>(request));</span><br><span class="line">        System.out.println(target);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyController</span> &#123;</span><br><span class="line">        <span class="meta">@InitBinder</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">aaa</span><span class="params">(WebDataBinder dataBinder)</span> &#123;</span><br><span class="line">            <span class="comment">// 扩展 dataBinder 的转换器</span></span><br><span class="line">            dataBinder.addCustomFormatter(<span class="keyword">new</span> <span class="title class_">MyDateFormatter</span>(<span class="string">&quot;用 @InitBinder 方式扩展的&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">private</span> Date birthday;</span><br><span class="line">        <span class="keyword">private</span> Address address;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> Address <span class="title function_">getAddress</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> address;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAddress</span><span class="params">(Address address)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.address = address;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> Date <span class="title function_">getBirthday</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> birthday;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBirthday</span><span class="params">(Date birthday)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.birthday = birthday;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;User&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;birthday=&quot;</span> + birthday +</span><br><span class="line">                   <span class="string">&quot;, address=&quot;</span> + address +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Address</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;Address&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;name=&#x27;&quot;</span> + name + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[DEBUG] 13:09:55.594 [main] com.itheima.a23.MyDateFormatter     - &gt;&gt;&gt;&gt;&gt;&gt; 进入了: 用 @InitBinder 方式扩展的 </span><br><span class="line">User&#123;birthday=Sat Jan 02 00:00:00 CST 1999, address=Address&#123;name=&#x27;西安&#x27;&#125;&#125;</span><br></pre></td></tr></table></figure>

<p>发现优先使用的是<code>@InitBinder</code>的方式进行转换的，这也符合高级类型转换接口的转换规则：</p>
<ul>
<li>首先看是否有自定义转换器, <code>@InitBinder</code>添加的即属于这种 (用了适配器模式把<code>Formatter</code>转为需要的 <code>PropertyEditor</code>)</li>
<li>再看有没有<code>ConversionService</code>转换</li>
<li>再利用默认的<code>PropertyEditor</code>转换</li>
<li>最后有一些特殊处理</li>
</ul>
<ol start="3">
<li>使用默认的<code>ConversionService</code>进行转换：</li>
</ol>
<p>默认的<code>ConversionService</code>转换需要配合注解<code>@DateTimeFormat</code>使用：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestServletDataBinderFactory</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        request.setParameter(<span class="string">&quot;birthday&quot;</span>, <span class="string">&quot;1999|01|02&quot;</span>);</span><br><span class="line">        request.setParameter(<span class="string">&quot;address.name&quot;</span>, <span class="string">&quot;西安&quot;</span>);</span><br><span class="line">        <span class="type">User</span> <span class="variable">target</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        <span class="type">DefaultFormattingConversionService</span> <span class="variable">service</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultFormattingConversionService</span>();</span><br><span class="line">        <span class="comment">// 如果是SpringBoot程序还可以使用ApplicationConversionService实现</span></span><br><span class="line">        <span class="comment">// ApplicationConversionService service = new ApplicationConversionService();</span></span><br><span class="line">        <span class="type">ConfigurableWebBindingInitializer</span> <span class="variable">initializer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ConfigurableWebBindingInitializer</span>();</span><br><span class="line">        initializer.setConversionService(service);</span><br><span class="line">        <span class="type">ServletRequestDataBinderFactory</span> <span class="variable">factory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletRequestDataBinderFactory</span>(<span class="literal">null</span>, initializer);</span><br><span class="line">        <span class="type">WebDataBinder</span> <span class="variable">dataBinder</span> <span class="operator">=</span> factory.createBinder(<span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request), target, <span class="string">&quot;user&quot;</span>);</span><br><span class="line">        dataBinder.bind(<span class="keyword">new</span> <span class="title class_">ServletRequestParameterPropertyValues</span>(request));</span><br><span class="line">        System.out.println(target);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">        <span class="meta">@DateTimeFormat(pattern = &quot;yyyy|MM|dd&quot;)</span></span><br><span class="line">        <span class="keyword">private</span> Date birthday;</span><br><span class="line">        <span class="keyword">private</span> Address address;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> Address <span class="title function_">getAddress</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> address;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAddress</span><span class="params">(Address address)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.address = address;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> Date <span class="title function_">getBirthday</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> birthday;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBirthday</span><span class="params">(Date birthday)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.birthday = birthday;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;User&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;birthday=&quot;</span> + birthday +</span><br><span class="line">                   <span class="string">&quot;, address=&quot;</span> + address +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Address</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;Address&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;name=&#x27;&quot;</span> + name + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">User&#123;birthday=Sat Jan 02 00:00:00 CST 1999, address=Address&#123;name=&#x27;西安&#x27;&#125;&#125;</span><br></pre></td></tr></table></figure>

<h2 id="获取泛型参数"><a href="#获取泛型参数" class="headerlink" title="获取泛型参数"></a>获取泛型参数</h2><p>获取泛型参数有两种方法：</p>
<ol>
<li>使用<code>jdk</code>的原始<code>api</code></li>
<li>使用<code>spring</code>提供的工具类</li>
</ol>
<p>创建示例代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">BaseDao</span>&lt;T&gt; &#123;</span><br><span class="line">    T <span class="title function_">findOne</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">EmployeeDao</span> <span class="keyword">extends</span> <span class="title class_">BaseDao</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Student</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">StudentDao</span> <span class="keyword">extends</span> <span class="title class_">BaseDao</span>&lt;Student&gt; &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Teacher</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TeacherDao</span> <span class="keyword">extends</span> <span class="title class_">BaseDao</span>&lt;Teacher&gt; &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ol>
<li>使用<code>jdk</code>的原始<code>api</code>：</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestGenericType</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. java api</span></span><br><span class="line">        <span class="type">Type</span> <span class="variable">type</span> <span class="operator">=</span> TeacherDao.class.getGenericSuperclass();</span><br><span class="line">        System.out.println(type);</span><br><span class="line">        <span class="comment">// 判断type是否带有泛型信息，例如EmployeeDao继承了BaseDao但是却没有泛型信息</span></span><br><span class="line">        <span class="keyword">if</span> (type <span class="keyword">instanceof</span> ParameterizedType parameterizedType) &#123;</span><br><span class="line">            System.out.println(parameterizedType.getActualTypeArguments()[<span class="number">0</span>]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">com.itheima.a23.sub.BaseDao&lt;com.itheima.a23.sub.Teacher&gt;</span><br><span class="line">class com.itheima.a23.sub.Teacher</span><br></pre></td></tr></table></figure>

<ol start="2">
<li>使用<code>spring</code>提供的工具类</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestGenericType</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 2. spring api 1</span></span><br><span class="line">        Class&lt;?&gt; t = GenericTypeResolver.resolveTypeArgument(TeacherDao.class, BaseDao.class);</span><br><span class="line">        System.out.println(t);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">class com.itheima.a23.sub.Teacher</span><br></pre></td></tr></table></figure>

<p>如果有多个泛型参数的话，可以使用<code>GenericTypeResolver</code>的<code>resolveTypeArgument()</code>方法</p>
<h1 id="InitBinder注解"><a href="#InitBinder注解" class="headerlink" title="@InitBinder注解"></a>@InitBinder注解</h1><p><code>@ControllerAdvice</code>可以为控制器提供增强功能，它能提供以下三种功能增强：</p>
<ol>
<li><code>@ExceptionHandler</code>：加在方法上，统一异常处理</li>
<li><code>@ModelAttribute</code>：加载方法上，方法的返回值将作为模型数据补充到控制器的执行过程中</li>
<li><code>@InitBinder</code>：自定义类型转换器</li>
</ol>
<p>这里主要介绍<code>@InitBinder</code>注解，<code>@InitBinder</code>注解可以在两个地方使用：</p>
<ol>
<li>加在<code>@ControllerAdvice</code>注解修饰的类中的方法上，对所有<code>Controller</code>都生效。</li>
<li>加在<code>@Controller</code>注解修饰的类中的方法上，只对当前<code>Controller</code>生效。</li>
</ol>
<p>初始化流程大致为先解析<code>@InitBinder</code>注解，再执行对应的<code>Handler</code>方法，查看<code>RequestMappingHandlerAdapter</code>源码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 每个控制器中的initBinder解析缓存</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map&lt;Class&lt;?&gt;, Set&lt;Method&gt;&gt; initBinderCache = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>(<span class="number">64</span>);</span><br><span class="line"><span class="comment">// 全局的initBinder解析缓存</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map&lt;ControllerAdviceBean, Set&lt;Method&gt;&gt; initBinderAdviceCache = <span class="keyword">new</span> <span class="title class_">LinkedHashMap</span>();</span><br></pre></td></tr></table></figure>

<p>准备代码示例：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 配置类</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ControllerAdvice</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyControllerAdvice</span> &#123;</span><br><span class="line">        <span class="meta">@InitBinder</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">binder3</span><span class="params">(WebDataBinder webDataBinder)</span> &#123;</span><br><span class="line">            webDataBinder.addCustomFormatter(<span class="keyword">new</span> <span class="title class_">MyDateFormatter</span>(<span class="string">&quot;binder3 转换器&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Controller</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller1</span> &#123;</span><br><span class="line">        <span class="meta">@InitBinder</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">binder1</span><span class="params">(WebDataBinder webDataBinder)</span> &#123;</span><br><span class="line">            webDataBinder.addCustomFormatter(<span class="keyword">new</span> <span class="title class_">MyDateFormatter</span>(<span class="string">&quot;binder1 转换器&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">foo</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Controller</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller2</span> &#123;</span><br><span class="line">        <span class="meta">@InitBinder</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">binder21</span><span class="params">(WebDataBinder webDataBinder)</span> &#123;</span><br><span class="line">            webDataBinder.addCustomFormatter(<span class="keyword">new</span> <span class="title class_">MyDateFormatter</span>(<span class="string">&quot;binder21 转换器&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@InitBinder</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">binder22</span><span class="params">(WebDataBinder webDataBinder)</span> &#123;</span><br><span class="line">            webDataBinder.addCustomFormatter(<span class="keyword">new</span> <span class="title class_">MyDateFormatter</span>(<span class="string">&quot;binder22 转换器&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bar</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 启动类</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A24</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(A24.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">AnnotationConfigApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span></span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">AnnotationConfigApplicationContext</span>(WebConfig.class);</span><br><span class="line">        <span class="type">RequestMappingHandlerAdapter</span> <span class="variable">handlerAdapter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RequestMappingHandlerAdapter</span>();</span><br><span class="line">        handlerAdapter.setApplicationContext(context);</span><br><span class="line">        handlerAdapter.afterPropertiesSet();</span><br><span class="line"></span><br><span class="line">        log.debug(<span class="string">&quot;1. 刚开始...&quot;</span>);</span><br><span class="line">        showBindMethods(handlerAdapter);</span><br><span class="line"></span><br><span class="line">        <span class="type">Method</span> <span class="variable">getDataBinderFactory</span> <span class="operator">=</span> RequestMappingHandlerAdapter.class.getDeclaredMethod(<span class="string">&quot;getDataBinderFactory&quot;</span>, HandlerMethod.class);</span><br><span class="line">        getDataBinderFactory.setAccessible(<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">        log.debug(<span class="string">&quot;2. 模拟调用 Controller1 的 foo 方法时 ...&quot;</span>);</span><br><span class="line">        getDataBinderFactory.invoke(handlerAdapter, <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(<span class="keyword">new</span> <span class="title class_">WebConfig</span>.Controller1(), WebConfig.Controller1.class.getMethod(<span class="string">&quot;foo&quot;</span>)));</span><br><span class="line">        showBindMethods(handlerAdapter);</span><br><span class="line"></span><br><span class="line">        log.debug(<span class="string">&quot;3. 模拟调用 Controller2 的 bar 方法时 ...&quot;</span>);</span><br><span class="line">        getDataBinderFactory.invoke(handlerAdapter, <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(<span class="keyword">new</span> <span class="title class_">WebConfig</span>.Controller2(), WebConfig.Controller2.class.getMethod(<span class="string">&quot;bar&quot;</span>)));</span><br><span class="line">        showBindMethods(handlerAdapter);</span><br><span class="line"></span><br><span class="line">        context.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;all&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">showBindMethods</span><span class="params">(RequestMappingHandlerAdapter handlerAdapter)</span> <span class="keyword">throws</span> NoSuchFieldException, IllegalAccessException &#123;</span><br><span class="line">        <span class="type">Field</span> <span class="variable">initBinderAdviceCache</span> <span class="operator">=</span> RequestMappingHandlerAdapter.class.getDeclaredField(<span class="string">&quot;initBinderAdviceCache&quot;</span>);</span><br><span class="line">        initBinderAdviceCache.setAccessible(<span class="literal">true</span>);</span><br><span class="line">        Map&lt;ControllerAdviceBean, Set&lt;Method&gt;&gt; globalMap = (Map&lt;ControllerAdviceBean, Set&lt;Method&gt;&gt;) initBinderAdviceCache.get(handlerAdapter);</span><br><span class="line">        log.debug(<span class="string">&quot;全局的 @InitBinder 方法 &#123;&#125;&quot;</span>,</span><br><span class="line">                globalMap.values().stream()</span><br><span class="line">                        .flatMap(ms -&gt; ms.stream().map(m -&gt; m.getName()))</span><br><span class="line">                        .collect(Collectors.toList())</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="type">Field</span> <span class="variable">initBinderCache</span> <span class="operator">=</span> RequestMappingHandlerAdapter.class.getDeclaredField(<span class="string">&quot;initBinderCache&quot;</span>);</span><br><span class="line">        initBinderCache.setAccessible(<span class="literal">true</span>);</span><br><span class="line">        Map&lt;Class&lt;?&gt;, Set&lt;Method&gt;&gt; controllerMap = (Map&lt;Class&lt;?&gt;, Set&lt;Method&gt;&gt;) initBinderCache.get(handlerAdapter);</span><br><span class="line">        log.debug(<span class="string">&quot;控制器的 @InitBinder 方法 &#123;&#125;&quot;</span>,</span><br><span class="line">                controllerMap.entrySet().stream()</span><br><span class="line">                        .flatMap(e -&gt; e.getValue().stream().map(v -&gt; e.getKey().getSimpleName() + <span class="string">&quot;.&quot;</span> + v.getName()))</span><br><span class="line">                        .collect(Collectors.toList())</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[DEBUG] 16:20:11.370 [main] com.itheima.a24.A24                 - 1. 刚开始... </span><br><span class="line">[DEBUG] 16:20:11.376 [main] com.itheima.a24.A24                 - 全局的 @InitBinder 方法 [binder3] </span><br><span class="line">[DEBUG] 16:20:11.378 [main] com.itheima.a24.A24                 - 控制器的 @InitBinder 方法 [] </span><br><span class="line">[DEBUG] 16:20:11.379 [main] com.itheima.a24.A24                 - 2. 模拟调用 Controller1 的 foo 方法时 ... </span><br><span class="line">[DEBUG] 16:20:11.384 [main] com.itheima.a24.A24                 - 全局的 @InitBinder 方法 [binder3] </span><br><span class="line">[DEBUG] 16:20:11.387 [main] com.itheima.a24.A24                 - 控制器的 @InitBinder 方法 [Controller1.binder1] </span><br><span class="line">[DEBUG] 16:20:11.387 [main] com.itheima.a24.A24                 - 3. 模拟调用 Controller2 的 bar 方法时 ... </span><br><span class="line">[DEBUG] 16:20:11.388 [main] com.itheima.a24.A24                 - 全局的 @InitBinder 方法 [binder3] </span><br><span class="line">[DEBUG] 16:20:11.388 [main] com.itheima.a24.A24                 - 控制器的 @InitBinder 方法 [Controller1.binder1, Controller2.binder22, Controller2.binder21] </span><br></pre></td></tr></table></figure>

<p>观察输出，我们发现：</p>
<ol>
<li><code>@ControllerAdvice</code>中<code>@InitBinder</code>标注的方法，由<code>RequestMappingHandlerAdapter</code>在<strong>初始化</strong>时解析并记录</li>
<li><code>@Controller</code>中<code>@InitBinder</code>标注的方法，由<code>RequestMappingHandlerAdapter</code>会<strong>在控制器方法首次执行时</strong>解析并记录</li>
</ol>
<h1 id="控制器方法执行流程"><a href="#控制器方法执行流程" class="headerlink" title="控制器方法执行流程"></a>控制器方法执行流程</h1><p><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/5.png" alt="img"></p>
<p><code>HandlerMethod</code>需要：</p>
<ul>
<li><code>bean</code>即是哪个<code>Controller</code></li>
<li><code>method</code>即是<code>Controller</code>中的哪个方法</li>
</ul>
<p><code>ServletInvocableHandlerMethod</code>需要：</p>
<ul>
<li><code>WebDataBinderFactory</code>负责对象绑定、类型转换</li>
<li><code>ParameterNameDiscoverer</code>负责参数名解析</li>
<li><code>HandlerMethodArgumentResolverComposite</code>负责解析参数</li>
<li><code>HandlerMethodReturnValueHandlerComposite</code>负责处理返回值</li>
</ul>
<p>控制器方法执行流程如下图所示：</p>
<p><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/6.png" alt="img"></p>
<p><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/7.png" alt="img"></p>
<p>准备代码示例：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 配置类</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Controller</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller1</span> &#123;</span><br><span class="line">        <span class="meta">@ResponseStatus(HttpStatus.OK)</span></span><br><span class="line">        <span class="keyword">public</span> ModelAndView <span class="title function_">foo</span><span class="params">(User user)</span> &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;foo&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;User&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;name=&#x27;&quot;</span> + name + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 启动类</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A25</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">AnnotationConfigApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span></span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">AnnotationConfigApplicationContext</span>(WebConfig.class);</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        request.setParameter(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">        <span class="comment">// 现在可以通过 ServletInvocableHandlerMethod 把这些整合在一起, 并完成控制器方法的调用, 如下</span></span><br><span class="line">        <span class="type">ServletInvocableHandlerMethod</span> <span class="variable">handlerMethod</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletInvocableHandlerMethod</span>(</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">Controller1</span>(), Controller1.class.getMethod(<span class="string">&quot;foo&quot;</span>, User.class));</span><br><span class="line">        <span class="type">ServletRequestDataBinderFactory</span> <span class="variable">factory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletRequestDataBinderFactory</span>(<span class="literal">null</span>, <span class="literal">null</span>);</span><br><span class="line">        handlerMethod.setDataBinderFactory(factory);</span><br><span class="line">        handlerMethod.setParameterNameDiscoverer(<span class="keyword">new</span> <span class="title class_">DefaultParameterNameDiscoverer</span>());</span><br><span class="line">        handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));</span><br><span class="line">        <span class="type">ModelAndViewContainer</span> <span class="variable">container</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ModelAndViewContainer</span>();</span><br><span class="line">        handlerMethod.invokeAndHandle(<span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request), container);</span><br><span class="line">        System.out.println(container.getModel());</span><br><span class="line">        context.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> HandlerMethodArgumentResolverComposite <span class="title function_">getArgumentResolvers</span><span class="params">(AnnotationConfigApplicationContext context)</span> &#123;</span><br><span class="line">        <span class="type">HandlerMethodArgumentResolverComposite</span> <span class="variable">composite</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethodArgumentResolverComposite</span>();</span><br><span class="line">        <span class="comment">// 添加参数解析器</span></span><br><span class="line">        composite.addResolvers(</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">RequestParamMethodArgumentResolver</span>(context.getDefaultListableBeanFactory(), <span class="literal">false</span>),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">PathVariableMethodArgumentResolver</span>(),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">RequestHeaderMethodArgumentResolver</span>(context.getDefaultListableBeanFactory()),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ServletCookieValueMethodArgumentResolver</span>(context.getDefaultListableBeanFactory()),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ExpressionValueMethodArgumentResolver</span>(context.getDefaultListableBeanFactory()),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ServletRequestMethodArgumentResolver</span>(),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">false</span>),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">RequestResponseBodyMethodProcessor</span>(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>())),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">true</span>),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">RequestParamMethodArgumentResolver</span>(context.getDefaultListableBeanFactory(), <span class="literal">true</span>)</span><br><span class="line">        );</span><br><span class="line">        <span class="keyword">return</span> composite;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">foo</span><br><span class="line">&#123;user=User&#123;name=&#x27;张三&#x27;&#125;, org.springframework.validation.BindingResult.user=org.springframework.validation.BeanPropertyBindingResult: 0 errors&#125;</span><br></pre></td></tr></table></figure>

<p>发现调用了目标方法，并且将<code>@ModelAttribute</code>注解解析的数据放入了<code>Model</code>中。</p>
<h1 id="ModelAttribute注解"><a href="#ModelAttribute注解" class="headerlink" title="@ModelAttribute注解"></a>@ModelAttribute注解</h1><p><code>@ModelAttribute</code>注解可以在三个地方使用：</p>
<ol>
<li>添加到<code>Controller</code>中的方法参数列表上：</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller1</span> &#123;</span><br><span class="line">    <span class="meta">@ResponseStatus(HttpStatus.OK)</span></span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">foo</span><span class="params">(<span class="meta">@ModelAttribute(&quot;u&quot;)</span> User user)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;foo&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这种方式由参数解析器<code>ServletModelAttributeMethodProcessor</code>解析。</p>
<ol start="2">
<li>添加到<code>ControllerAdvice</code>中的方法上，对于全局都有效：</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ControllerAdvice</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyControllerAdvice</span> &#123;</span><br><span class="line">    <span class="meta">@ModelAttribute(&quot;a&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">aa</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;aa&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这种方式由<code>RequestMappingHandlerAdapter</code>来解析。</p>
<ol start="3">
<li>添加到<code>Controller</code>中的方法上，对于当前<code>Controller</code>有效：</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller1</span> &#123;</span><br><span class="line">    <span class="meta">@ModelAttribute(&quot;b&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">aa</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;bb&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这种方式由<code>RequestMappingHandlerAdapter</code>来解析。</p>
<p>以上几种方式的功能一样，都是解析数据并添加到<code>ModelAndViewContainer</code>中。</p>
<p>准备代码示例：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 配置类</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ControllerAdvice</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyControllerAdvice</span> &#123;</span><br><span class="line">        <span class="meta">@ModelAttribute(&quot;a&quot;)</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">aa</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;aa&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Controller</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller1</span> &#123;</span><br><span class="line">        <span class="meta">@ModelAttribute(&quot;b&quot;)</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">aa</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;bb&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@ResponseStatus(HttpStatus.OK)</span></span><br><span class="line">        <span class="keyword">public</span> ModelAndView <span class="title function_">foo</span><span class="params">(<span class="meta">@ModelAttribute(&quot;u&quot;)</span> User user)</span> &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;foo&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;User&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;name=&#x27;&quot;</span> + name + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 启动类</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A26</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">AnnotationConfigApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span></span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">AnnotationConfigApplicationContext</span>(WebConfig.class);</span><br><span class="line"></span><br><span class="line">        <span class="type">RequestMappingHandlerAdapter</span> <span class="variable">adapter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RequestMappingHandlerAdapter</span>();</span><br><span class="line">        adapter.setApplicationContext(context);</span><br><span class="line">        <span class="comment">// 将会初始化ControllerAdviceCache</span></span><br><span class="line">        adapter.afterPropertiesSet();</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        request.setParameter(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">        <span class="comment">// 现在可以通过 ServletInvocableHandlerMethod 把这些整合在一起, 并完成控制器方法的调用, 如下</span></span><br><span class="line">        <span class="type">ServletInvocableHandlerMethod</span> <span class="variable">handlerMethod</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletInvocableHandlerMethod</span>(</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">Controller1</span>(), Controller1.class.getMethod(<span class="string">&quot;foo&quot;</span>, User.class));</span><br><span class="line">        <span class="type">ServletRequestDataBinderFactory</span> <span class="variable">factory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletRequestDataBinderFactory</span>(<span class="literal">null</span>, <span class="literal">null</span>);</span><br><span class="line">        handlerMethod.setDataBinderFactory(factory);</span><br><span class="line">        handlerMethod.setParameterNameDiscoverer(<span class="keyword">new</span> <span class="title class_">DefaultParameterNameDiscoverer</span>());</span><br><span class="line">        handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));</span><br><span class="line">        <span class="type">ModelAndViewContainer</span> <span class="variable">container</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ModelAndViewContainer</span>();</span><br><span class="line">        <span class="comment">// 获取模型工厂方法</span></span><br><span class="line">        <span class="type">Method</span> <span class="variable">getModelFactory</span> <span class="operator">=</span> RequestMappingHandlerAdapter.class.getDeclaredMethod(<span class="string">&quot;getModelFactory&quot;</span>, HandlerMethod.class, WebDataBinderFactory.class);</span><br><span class="line">        getModelFactory.setAccessible(<span class="literal">true</span>);</span><br><span class="line">        <span class="type">ModelFactory</span> <span class="variable">modelFactory</span> <span class="operator">=</span> (ModelFactory) getModelFactory.invoke(adapter, handlerMethod, factory);</span><br><span class="line">        <span class="comment">// 初始化模型数据</span></span><br><span class="line">        modelFactory.initModel(<span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request), container, handlerMethod);</span><br><span class="line">        handlerMethod.invokeAndHandle(<span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request), container);</span><br><span class="line">        System.out.println(container.getModel());</span><br><span class="line">        context.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> HandlerMethodArgumentResolverComposite <span class="title function_">getArgumentResolvers</span><span class="params">(AnnotationConfigApplicationContext context)</span> &#123;</span><br><span class="line">        <span class="type">HandlerMethodArgumentResolverComposite</span> <span class="variable">composite</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethodArgumentResolverComposite</span>();</span><br><span class="line">        <span class="comment">// 添加参数解析器</span></span><br><span class="line">        composite.addResolvers(</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">RequestParamMethodArgumentResolver</span>(context.getDefaultListableBeanFactory(), <span class="literal">false</span>),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">PathVariableMethodArgumentResolver</span>(),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">RequestHeaderMethodArgumentResolver</span>(context.getDefaultListableBeanFactory()),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ServletCookieValueMethodArgumentResolver</span>(context.getDefaultListableBeanFactory()),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ExpressionValueMethodArgumentResolver</span>(context.getDefaultListableBeanFactory()),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ServletRequestMethodArgumentResolver</span>(),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">false</span>),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">RequestResponseBodyMethodProcessor</span>(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>())),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">true</span>),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">RequestParamMethodArgumentResolver</span>(context.getDefaultListableBeanFactory(), <span class="literal">true</span>)</span><br><span class="line">        );</span><br><span class="line">        <span class="keyword">return</span> composite;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">foo</span><br><span class="line">&#123;a=aa, b=bb, u=User&#123;name=&#x27;张三&#x27;&#125;, org.springframework.validation.BindingResult.u=org.springframework.validation.BeanPropertyBindingResult: 0 errors&#125;</span><br></pre></td></tr></table></figure>

<p>所有的<code>@ModelAttribute</code>注解都已经被解析。</p>
<h1 id="返回值处理器"><a href="#返回值处理器" class="headerlink" title="返回值处理器"></a>返回值处理器</h1><p><code>RequestMappingHandlerAdapter</code>自带的返回值处理器：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@1f44ddab</span><br><span class="line">org.springframework.web.method.annotation.ModelMethodProcessor@5017e1</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@65b66b08</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@4726927c</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@7eb6b6b6</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@7ed9499e</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler@28e19366</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler@5b275174</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler@10ef5fa0</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler@244e619a</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@10acd6</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@61dde151</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler@b25b095</span><br><span class="line">org.springframework.web.method.annotation.MapMethodProcessor@5cb042da</span><br><span class="line">org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@59c33386</span><br></pre></td></tr></table></figure>

<p>准备测试代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 模板解析配置</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> FreeMarkerConfigurer <span class="title function_">freeMarkerConfigurer</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">FreeMarkerConfigurer</span> <span class="variable">configurer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FreeMarkerConfigurer</span>();</span><br><span class="line">        configurer.setDefaultEncoding(<span class="string">&quot;utf-8&quot;</span>);</span><br><span class="line">        configurer.setTemplateLoaderPath(<span class="string">&quot;classpath:templates&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> configurer;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span> <span class="comment">// FreeMarkerView 在借助 Spring 初始化时，会要求 web 环境才会走 setConfiguration, 这里想办法去掉了 web 环境的约束</span></span><br><span class="line">    <span class="keyword">public</span> FreeMarkerViewResolver <span class="title function_">viewResolver</span><span class="params">(FreeMarkerConfigurer configurer)</span> &#123;</span><br><span class="line">        <span class="type">FreeMarkerViewResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FreeMarkerViewResolver</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">protected</span> AbstractUrlBasedView <span class="title function_">instantiateView</span><span class="params">()</span> &#123;</span><br><span class="line">                <span class="type">FreeMarkerView</span> <span class="variable">view</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FreeMarkerView</span>() &#123;</span><br><span class="line">                    <span class="meta">@Override</span></span><br><span class="line">                    <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">isContextRequired</span><span class="params">()</span> &#123;</span><br><span class="line">                        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;;</span><br><span class="line">                view.setConfiguration(configurer.getConfiguration());</span><br><span class="line">                <span class="keyword">return</span> view;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        resolver.setContentType(<span class="string">&quot;text/html;charset=utf-8&quot;</span>);</span><br><span class="line">        resolver.setPrefix(<span class="string">&quot;/&quot;</span>);</span><br><span class="line">        resolver.setSuffix(<span class="string">&quot;.ftl&quot;</span>);</span><br><span class="line">        resolver.setExposeSpringMacroHelpers(<span class="literal">false</span>);</span><br><span class="line">        <span class="keyword">return</span> resolver;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 启动类</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A27</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(A27.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">AnnotationConfigApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span></span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">AnnotationConfigApplicationContext</span>(WebConfig.class);</span><br><span class="line">        <span class="comment">// 测试参数解析器</span></span><br><span class="line">        testXxx(context);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> HandlerMethodReturnValueHandlerComposite <span class="title function_">getReturnValueHandler</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">HandlerMethodReturnValueHandlerComposite</span> <span class="variable">composite</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethodReturnValueHandlerComposite</span>();</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">ModelAndViewMethodReturnValueHandler</span>());</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">ViewNameMethodReturnValueHandler</span>());</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">false</span>));</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">HttpEntityMethodProcessor</span>(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>())));</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">HttpHeadersReturnValueHandler</span>());</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">RequestResponseBodyMethodProcessor</span>(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>())));</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">true</span>));</span><br><span class="line">        <span class="keyword">return</span> composite;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;all&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">renderView</span><span class="params">(AnnotationConfigApplicationContext context, ModelAndViewContainer container,</span></span><br><span class="line"><span class="params">                                   ServletWebRequest webRequest)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        log.debug(<span class="string">&quot;&gt;&gt;&gt;&gt;&gt;&gt; 渲染视图&quot;</span>);</span><br><span class="line">        <span class="type">FreeMarkerViewResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> context.getBean(FreeMarkerViewResolver.class);</span><br><span class="line">        <span class="type">String</span> <span class="variable">viewName</span> <span class="operator">=</span> container.getViewName() != <span class="literal">null</span> ? container.getViewName() : <span class="keyword">new</span> <span class="title class_">DefaultRequestToViewNameTranslator</span>().getViewName(webRequest.getRequest());</span><br><span class="line">        log.debug(<span class="string">&quot;没有获取到视图名, 采用默认视图名: &#123;&#125;&quot;</span>, viewName);</span><br><span class="line">        <span class="comment">// 每次渲染时, 会产生新的视图对象, 它并非被 Spring 所管理, 但确实借助了 Spring 容器来执行初始化</span></span><br><span class="line">        <span class="type">View</span> <span class="variable">view</span> <span class="operator">=</span> resolver.resolveViewName(viewName, Locale.getDefault());</span><br><span class="line">        view.render(container.getModel(), webRequest.getRequest(), webRequest.getResponse());</span><br><span class="line">        System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(((MockHttpServletResponse) webRequest.getResponse()).getContentAsByteArray(), StandardCharsets.UTF_8));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(Controller.class);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> ModelAndView <span class="title function_">test1</span><span class="params">()</span> &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;test1()&quot;</span>);</span><br><span class="line">            <span class="type">ModelAndView</span> <span class="variable">mav</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ModelAndView</span>(<span class="string">&quot;view1&quot;</span>);</span><br><span class="line">            mav.addObject(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> mav;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">test2</span><span class="params">()</span> &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;test2()&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;view2&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@ModelAttribute</span></span><br><span class="line"><span class="comment">//        @RequestMapping(&quot;/test3&quot;)</span></span><br><span class="line">        <span class="keyword">public</span> User <span class="title function_">test3</span><span class="params">()</span> &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;test3()&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;李四&quot;</span>, <span class="number">20</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> User <span class="title function_">test4</span><span class="params">()</span> &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;test4()&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;王五&quot;</span>, <span class="number">30</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> HttpEntity&lt;User&gt; <span class="title function_">test5</span><span class="params">()</span> &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;test5()&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">HttpEntity</span>&lt;&gt;(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;赵六&quot;</span>, <span class="number">40</span>));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> HttpHeaders <span class="title function_">test6</span><span class="params">()</span> &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;test6()&quot;</span>);</span><br><span class="line">            <span class="type">HttpHeaders</span> <span class="variable">headers</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HttpHeaders</span>();</span><br><span class="line">            headers.add(<span class="string">&quot;Content-Type&quot;</span>, <span class="string">&quot;text/html&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> headers;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@ResponseBody</span></span><br><span class="line">        <span class="keyword">public</span> User <span class="title function_">test7</span><span class="params">()</span> &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;test7()&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;钱七&quot;</span>, <span class="number">50</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 必须用 public 修饰, 否则 freemarker 渲染其 name, age 属性时失败</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">User</span><span class="params">(String name, <span class="type">int</span> age)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">            <span class="built_in">this</span>.age = age;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getAge</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> age;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAge</span><span class="params">(<span class="type">int</span> age)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.age = age;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;User&#123;&quot;</span> +</span><br><span class="line">                   <span class="string">&quot;name=&#x27;&quot;</span> + name + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">                   <span class="string">&quot;, age=&quot;</span> + age +</span><br><span class="line">                   <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>templates</code>代码：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--test3.ftl--&gt;</span></span><br><span class="line"><span class="meta">&lt;!doctype <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;zh&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>test3<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello! $&#123;user.name&#125; $&#123;user.age&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!--test4.ftl--&gt;</span></span><br><span class="line"><span class="meta">&lt;!doctype <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;zh&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>test4<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello! $&#123;user.name&#125; $&#123;user.age&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!--view1.ftl--&gt;</span></span><br><span class="line"><span class="meta">&lt;!doctype <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;zh&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>view1<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello! $&#123;name&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!--view2.ftl--&gt;</span></span><br><span class="line"><span class="meta">&lt;!doctype <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;zh&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>view2<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p><strong>ModelAndViewMethodReturnValueHandler</strong></p>
<p><code>ModelAndViewMethodReturnValueHandler</code>解析模型和视图，跳转到视图并且根据模型数据渲染视图。</p>
<p>添加测试方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 1. 测试返回值类型为 ModelAndView</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test1</span><span class="params">(AnnotationConfigApplicationContext context)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> Controller.class.getMethod(<span class="string">&quot;test1&quot;</span>);</span><br><span class="line">    <span class="type">Controller</span> <span class="variable">controller</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Controller</span>();</span><br><span class="line">    <span class="type">Object</span> <span class="variable">returnValue</span> <span class="operator">=</span> method.invoke(controller); <span class="comment">// 获取返回值</span></span><br><span class="line"></span><br><span class="line">    <span class="type">HandlerMethod</span> <span class="variable">methodHandle</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(controller, method);</span><br><span class="line"></span><br><span class="line">    <span class="type">ModelAndViewContainer</span> <span class="variable">container</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ModelAndViewContainer</span>();</span><br><span class="line">    <span class="type">HandlerMethodReturnValueHandlerComposite</span> <span class="variable">composite</span> <span class="operator">=</span> getReturnValueHandler();</span><br><span class="line">    <span class="type">ServletWebRequest</span> <span class="variable">webRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(<span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>(), <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>());</span><br><span class="line">    <span class="keyword">if</span> (composite.supportsReturnType(methodHandle.getReturnType())) &#123; <span class="comment">// 检查是否支持此类型的返回值</span></span><br><span class="line">        composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);</span><br><span class="line">        System.out.println(container.getModel());</span><br><span class="line">        System.out.println(container.getViewName());</span><br><span class="line">        renderView(context, container, webRequest); <span class="comment">// 渲染视图</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">[DEBUG] 21:01:41.269 [main] com.itheima.a27.A27$Controller      - test1() </span><br><span class="line">&#123;name=张三&#125;</span><br><span class="line">view1</span><br><span class="line">[DEBUG] 21:01:41.616 [main] com.itheima.a27.A27                 - &gt;&gt;&gt;&gt;&gt;&gt; 渲染视图 </span><br><span class="line">[DEBUG] 21:01:41.617 [main] com.itheima.a27.A27                 - 没有获取到视图名, 采用默认视图名: view1 </span><br><span class="line">[DEBUG] 21:01:41.667 [main] com.itheima.a27.WebConfig$1$1       - View name &#x27;view1&#x27;, model &#123;name=张三&#125; </span><br><span class="line">[DEBUG] 21:01:41.673 [main] com.itheima.a27.WebConfig$1$1       - Rendering [/view1.ftl] </span><br><span class="line">&lt;!doctype html&gt;</span><br><span class="line">&lt;html lang=&quot;zh&quot;&gt;</span><br><span class="line">&lt;head&gt;</span><br><span class="line">    &lt;meta charset=&quot;UTF-8&quot;&gt;</span><br><span class="line">    &lt;title&gt;view1&lt;/title&gt;</span><br><span class="line">&lt;/head&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">    &lt;h1&gt;Hello! 张三&lt;/h1&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure>

<p>发现能成功解析返回值并渲染视图。</p>
<p><strong>ViewNameMethodReturnValueHandler</strong></p>
<p><code>ViewNameMethodReturnValueHandler</code>解析视图，跳转到对应的视图。</p>
<p>添加测试方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 2. 测试返回值类型为 String 时, 把它当做视图名</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test2</span><span class="params">(AnnotationConfigApplicationContext context)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> Controller.class.getMethod(<span class="string">&quot;test2&quot;</span>);</span><br><span class="line">    <span class="type">Controller</span> <span class="variable">controller</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Controller</span>();</span><br><span class="line">    <span class="type">Object</span> <span class="variable">returnValue</span> <span class="operator">=</span> method.invoke(controller); <span class="comment">// 获取返回值</span></span><br><span class="line"></span><br><span class="line">    <span class="type">HandlerMethod</span> <span class="variable">methodHandle</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(controller, method);</span><br><span class="line"></span><br><span class="line">    <span class="type">ModelAndViewContainer</span> <span class="variable">container</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ModelAndViewContainer</span>();</span><br><span class="line">    <span class="type">HandlerMethodReturnValueHandlerComposite</span> <span class="variable">composite</span> <span class="operator">=</span> getReturnValueHandler();</span><br><span class="line">    <span class="type">ServletWebRequest</span> <span class="variable">webRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(<span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>(), <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>());</span><br><span class="line">    <span class="keyword">if</span> (composite.supportsReturnType(methodHandle.getReturnType())) &#123; <span class="comment">// 检查是否支持此类型的返回值</span></span><br><span class="line">        composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);</span><br><span class="line">        System.out.println(container.getModel());</span><br><span class="line">        System.out.println(container.getViewName());</span><br><span class="line">        renderView(context, container, webRequest); <span class="comment">// 渲染视图</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">[DEBUG] 21:07:58.480 [main] com.itheima.a27.A27$Controller      - test2() </span><br><span class="line">&#123;&#125;</span><br><span class="line">view2</span><br><span class="line">[DEBUG] 21:07:59.284 [main] com.itheima.a27.A27                 - &gt;&gt;&gt;&gt;&gt;&gt; 渲染视图 </span><br><span class="line">[DEBUG] 21:07:59.286 [main] com.itheima.a27.A27                 - 没有获取到视图名, 采用默认视图名: view2 </span><br><span class="line">[DEBUG] 21:07:59.438 [main] com.itheima.a27.WebConfig$1$1       - View name &#x27;view2&#x27;, model &#123;&#125; </span><br><span class="line">[DEBUG] 21:07:59.447 [main] com.itheima.a27.WebConfig$1$1       - Rendering [/view2.ftl] </span><br><span class="line">&lt;!doctype html&gt;</span><br><span class="line">&lt;html lang=&quot;zh&quot;&gt;</span><br><span class="line">&lt;head&gt;</span><br><span class="line">    &lt;meta charset=&quot;UTF-8&quot;&gt;</span><br><span class="line">    &lt;title&gt;view2&lt;/title&gt;</span><br><span class="line">&lt;/head&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">    &lt;h1&gt;Hello!&lt;/h1&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure>

<p>发现成功跳转到<code>view2</code>视图。</p>
<p><strong>ServletModelAttributeMethodProcessor</strong></p>
<p><code>ServletModelAttributeMethodProcessor</code>解析加在方法上的<code>@ModelAttribute</code>注解，跳转视图时按照方法上的<code>@RequestMapping</code>注解的值为准，这里没有<code>@RequestMapping</code>注解的解析环境，因此手动去添加视图。</p>
<p>添加测试方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 3. 测试返回值添加了 <span class="doctag">@ModelAttribute</span> 注解时, 此时需找到默认视图名</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test3</span><span class="params">(AnnotationConfigApplicationContext context)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> Controller.class.getMethod(<span class="string">&quot;test3&quot;</span>);</span><br><span class="line">    <span class="type">Controller</span> <span class="variable">controller</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Controller</span>();</span><br><span class="line">    <span class="type">Object</span> <span class="variable">returnValue</span> <span class="operator">=</span> method.invoke(controller); <span class="comment">// 获取返回值</span></span><br><span class="line"></span><br><span class="line">    <span class="type">HandlerMethod</span> <span class="variable">methodHandle</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(controller, method);</span><br><span class="line"></span><br><span class="line">    <span class="type">ModelAndViewContainer</span> <span class="variable">container</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ModelAndViewContainer</span>();</span><br><span class="line">    <span class="type">HandlerMethodReturnValueHandlerComposite</span> <span class="variable">composite</span> <span class="operator">=</span> getReturnValueHandler();</span><br><span class="line">    <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">    <span class="comment">// 视图解析时使用此名称</span></span><br><span class="line">    request.setRequestURI(<span class="string">&quot;/test3&quot;</span>);</span><br><span class="line">    UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);</span><br><span class="line">    <span class="type">ServletWebRequest</span> <span class="variable">webRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request, <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>());</span><br><span class="line">    <span class="keyword">if</span> (composite.supportsReturnType(methodHandle.getReturnType())) &#123; <span class="comment">// 检查是否支持此类型的返回值</span></span><br><span class="line">        composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);</span><br><span class="line">        System.out.println(container.getModel());</span><br><span class="line">        System.out.println(container.getViewName());</span><br><span class="line">        renderView(context, container, webRequest); <span class="comment">// 渲染视图</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">[DEBUG] 21:13:27.532 [main] com.itheima.a27.A27$Controller      - test3() </span><br><span class="line">&#123;user=User&#123;name=&#x27;李四&#x27;, age=20&#125;&#125;</span><br><span class="line">null</span><br><span class="line">[DEBUG] 21:13:27.799 [main] com.itheima.a27.A27                 - &gt;&gt;&gt;&gt;&gt;&gt; 渲染视图 </span><br><span class="line">[DEBUG] 21:13:27.802 [main] com.itheima.a27.A27                 - 没有获取到视图名, 采用默认视图名: test3 </span><br><span class="line">[DEBUG] 21:13:27.839 [main] com.itheima.a27.WebConfig$1$1       - View name &#x27;test3&#x27;, model &#123;user=User&#123;name=&#x27;李四&#x27;, age=20&#125;&#125; </span><br><span class="line">[DEBUG] 21:13:27.844 [main] com.itheima.a27.WebConfig$1$1       - Rendering [/test3.ftl] </span><br><span class="line">&lt;!doctype html&gt;</span><br><span class="line">&lt;html lang=&quot;zh&quot;&gt;</span><br><span class="line">&lt;head&gt;</span><br><span class="line">    &lt;meta charset=&quot;UTF-8&quot;&gt;</span><br><span class="line">    &lt;title&gt;test3&lt;/title&gt;</span><br><span class="line">&lt;/head&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">    &lt;h1&gt;Hello! 李四 20&lt;/h1&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure>

<p>在创建<code>ServletModelAttributeMethodProcessor</code>对象时，可以传入一个布尔值，如果为true代表可以省略方法上的<code>@ModelAttribute</code>注解，如果为false代表不能省略<code>@ModelAttribute</code>注解。</p>
<p><strong>HttpEntityMethodProcessor</strong></p>
<p><code>HttpEntityMethodProcessor</code>、<code>HttpHeadersReturnValueHandler</code>、<code>RequestResponseBodyMethodProcessor</code>都是只处理数据，不解析视图，在它们的<code>handleReturnValue()</code>方法中，都有以下操作：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 设置为true，因此后续操作不会进行视图解析以及渲染流程</span></span><br><span class="line">mavContainer.setRequestHandled(<span class="literal">true</span>);</span><br></pre></td></tr></table></figure>

<p><code>HttpEntityMethodProcessor</code>处理器可以返回响应的所有内容，包括响应头、响应体、状态码等。</p>
<p>添加测试方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 5. 测试返回值类型为 ResponseEntity 时, 此时不走视图流程</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test5</span><span class="params">(AnnotationConfigApplicationContext context)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> Controller.class.getMethod(<span class="string">&quot;test5&quot;</span>);</span><br><span class="line">    <span class="type">Controller</span> <span class="variable">controller</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Controller</span>();</span><br><span class="line">    <span class="type">Object</span> <span class="variable">returnValue</span> <span class="operator">=</span> method.invoke(controller); <span class="comment">// 获取返回值</span></span><br><span class="line"></span><br><span class="line">    <span class="type">HandlerMethod</span> <span class="variable">methodHandle</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(controller, method);</span><br><span class="line"></span><br><span class="line">    <span class="type">ModelAndViewContainer</span> <span class="variable">container</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ModelAndViewContainer</span>();</span><br><span class="line">    <span class="type">HandlerMethodReturnValueHandlerComposite</span> <span class="variable">composite</span> <span class="operator">=</span> getReturnValueHandler();</span><br><span class="line">    <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">    <span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line">    <span class="type">ServletWebRequest</span> <span class="variable">webRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request, response);</span><br><span class="line">    <span class="keyword">if</span> (composite.supportsReturnType(methodHandle.getReturnType())) &#123; <span class="comment">// 检查是否支持此类型的返回值</span></span><br><span class="line">        composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);</span><br><span class="line">        System.out.println(container.getModel());</span><br><span class="line">        System.out.println(container.getViewName());</span><br><span class="line">        <span class="keyword">if</span> (!container.isRequestHandled()) &#123;</span><br><span class="line">            renderView(context, container, webRequest); <span class="comment">// 渲染视图</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(response.getContentAsByteArray(), StandardCharsets.UTF_8));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[DEBUG] 21:25:55.986 [main] com.itheima.a27.A27$Controller      - test5() </span><br><span class="line">&#123;&#125;</span><br><span class="line">null</span><br><span class="line">&#123;&quot;name&quot;:&quot;赵六&quot;,&quot;age&quot;:40&#125;</span><br></pre></td></tr></table></figure>

<p>发现模型与视图都为空，而返回值为<code>json</code>格式的数据。</p>
<p><strong>HttpHeadersReturnValueHandler</strong></p>
<p><code>HttpHeadersReturnValueHandler</code>主要解析响应头和状态码。</p>
<p>添加测试方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 6. 测试返回值类型为 HttpHeaders 时, 此时不走视图流程</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test6</span><span class="params">(AnnotationConfigApplicationContext context)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> Controller.class.getMethod(<span class="string">&quot;test6&quot;</span>);</span><br><span class="line">    <span class="type">Controller</span> <span class="variable">controller</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Controller</span>();</span><br><span class="line">    <span class="type">Object</span> <span class="variable">returnValue</span> <span class="operator">=</span> method.invoke(controller); <span class="comment">// 获取返回值</span></span><br><span class="line"></span><br><span class="line">    <span class="type">HandlerMethod</span> <span class="variable">methodHandle</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(controller, method);</span><br><span class="line"></span><br><span class="line">    <span class="type">ModelAndViewContainer</span> <span class="variable">container</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ModelAndViewContainer</span>();</span><br><span class="line">    <span class="type">HandlerMethodReturnValueHandlerComposite</span> <span class="variable">composite</span> <span class="operator">=</span> getReturnValueHandler();</span><br><span class="line">    <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">    <span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line">    <span class="type">ServletWebRequest</span> <span class="variable">webRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request, response);</span><br><span class="line">    <span class="keyword">if</span> (composite.supportsReturnType(methodHandle.getReturnType())) &#123; <span class="comment">// 检查是否支持此类型的返回值</span></span><br><span class="line">        composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);</span><br><span class="line">        System.out.println(container.getModel());</span><br><span class="line">        System.out.println(container.getViewName());</span><br><span class="line">        <span class="keyword">if</span> (!container.isRequestHandled()) &#123;</span><br><span class="line">            renderView(context, container, webRequest); <span class="comment">// 渲染视图</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (String name : response.getHeaderNames()) &#123;</span><br><span class="line">                System.out.println(name + <span class="string">&quot;=&quot;</span> + response.getHeader(name));</span><br><span class="line">            &#125;</span><br><span class="line">            System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(response.getContentAsByteArray(), StandardCharsets.UTF_8));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[DEBUG] 21:28:51.932 [main] com.itheima.a27.A27$Controller      - test6() </span><br><span class="line">&#123;&#125;</span><br><span class="line">null</span><br><span class="line">Content-Type=text/html</span><br></pre></td></tr></table></figure>

<p>发现模型与视图都为空，而响应头中的内容以及被打印。</p>
<p><strong>RequestResponseBodyMethodProcessor</strong></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">RequestResponseBodyMethodProcessor`主要解析响应体内容，它会默认在响应头中给我们添加`Content-Type=application/json</span><br></pre></td></tr></table></figure>

<p>添加测试方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 7. 测试返回值添加了 <span class="doctag">@ResponseBody</span> 注解时, 此时不走视图流程</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test7</span><span class="params">(AnnotationConfigApplicationContext context)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> Controller.class.getMethod(<span class="string">&quot;test7&quot;</span>);</span><br><span class="line">    <span class="type">Controller</span> <span class="variable">controller</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Controller</span>();</span><br><span class="line">    <span class="type">Object</span> <span class="variable">returnValue</span> <span class="operator">=</span> method.invoke(controller); <span class="comment">// 获取返回值</span></span><br><span class="line"></span><br><span class="line">    <span class="type">HandlerMethod</span> <span class="variable">methodHandle</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(controller, method);</span><br><span class="line"></span><br><span class="line">    <span class="type">ModelAndViewContainer</span> <span class="variable">container</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ModelAndViewContainer</span>();</span><br><span class="line">    <span class="type">HandlerMethodReturnValueHandlerComposite</span> <span class="variable">composite</span> <span class="operator">=</span> getReturnValueHandler();</span><br><span class="line">    <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">    <span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line">    <span class="type">ServletWebRequest</span> <span class="variable">webRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request, response);</span><br><span class="line">    <span class="keyword">if</span> (composite.supportsReturnType(methodHandle.getReturnType())) &#123; <span class="comment">// 检查是否支持此类型的返回值</span></span><br><span class="line">        composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);</span><br><span class="line">        System.out.println(container.getModel());</span><br><span class="line">        System.out.println(container.getViewName());</span><br><span class="line">        <span class="keyword">if</span> (!container.isRequestHandled()) &#123;</span><br><span class="line">            renderView(context, container, webRequest); <span class="comment">// 渲染视图</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (String name : response.getHeaderNames()) &#123;</span><br><span class="line">                System.out.println(name + <span class="string">&quot;=&quot;</span> + response.getHeader(name));</span><br><span class="line">            &#125;</span><br><span class="line">            System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(response.getContentAsByteArray(), StandardCharsets.UTF_8));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[DEBUG] 21:30:50.557 [main] com.itheima.a27.A27$Controller      - test7() </span><br><span class="line">&#123;&#125;</span><br><span class="line">null</span><br><span class="line">Content-Type=application/json</span><br><span class="line">&#123;&quot;name&quot;:&quot;钱七&quot;,&quot;age&quot;:50&#125;</span><br></pre></td></tr></table></figure>

<h1 id="MessageConverter"><a href="#MessageConverter" class="headerlink" title="MessageConverter"></a><strong>MessageConverter</strong></h1><p>在学习<code>HttpEntityMethodProcessor</code>处理器和<code>RequestResponseBodyMethodProcessor</code>时，创建这两个处理器我们都会添加一个<code>MappingJackson2HttpMessageConverter</code>。<code>MappingJackson2HttpMessageConverter</code>的作用是将返回值转换为<code>json</code>格式。</p>
<p>测试<code>MessageConverter</code>的用法，准备实体类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@JsonCreator</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">User</span><span class="params">(<span class="meta">@JsonProperty(&quot;name&quot;)</span> String name, <span class="meta">@JsonProperty(&quot;age&quot;)</span> <span class="type">int</span> age)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">        <span class="built_in">this</span>.age = age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getAge</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAge</span><span class="params">(<span class="type">int</span> age)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.age = age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;User&#123;&quot;</span> +</span><br><span class="line">               <span class="string">&quot;name=&#x27;&quot;</span> + name + <span class="string">&#x27;\&#x27;&#x27;</span> +</span><br><span class="line">               <span class="string">&quot;, age=&quot;</span> + age +</span><br><span class="line">               <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ol>
<li>测试将对象转化为json格式：</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test1</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">MockHttpOutputMessage</span> <span class="variable">message</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpOutputMessage</span>();</span><br><span class="line">    <span class="type">MappingJackson2HttpMessageConverter</span> <span class="variable">converter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>();</span><br><span class="line">    <span class="keyword">if</span> (converter.canWrite(User.class, MediaType.APPLICATION_JSON)) &#123;</span><br><span class="line">        converter.write(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;张三&quot;</span>, <span class="number">18</span>), MediaType.APPLICATION_JSON, message);</span><br><span class="line">        System.out.println(message.getBodyAsString());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;name&quot;:&quot;张三&quot;,&quot;age&quot;:18&#125;</span><br></pre></td></tr></table></figure>

<ol start="2">
<li>测试将对象转为<code>xml</code>格式：</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test2</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">MockHttpOutputMessage</span> <span class="variable">message</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpOutputMessage</span>();</span><br><span class="line">    <span class="type">MappingJackson2XmlHttpMessageConverter</span> <span class="variable">converter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MappingJackson2XmlHttpMessageConverter</span>();</span><br><span class="line">    <span class="keyword">if</span> (converter.canWrite(User.class, MediaType.APPLICATION_XML)) &#123;</span><br><span class="line">        converter.write(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;李四&quot;</span>, <span class="number">20</span>), MediaType.APPLICATION_XML, message);</span><br><span class="line">        System.out.println(message.getBodyAsString());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;User&gt;&lt;name&gt;李四&lt;/name&gt;&lt;age&gt;20&lt;/age&gt;&lt;/User&gt;</span><br></pre></td></tr></table></figure>

<ol start="3">
<li>测试将<code>json</code>格式数据转换为<code>java</code>对象：</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test3</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">MockHttpInputMessage</span> <span class="variable">message</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpInputMessage</span>(<span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">            &#123;</span></span><br><span class="line"><span class="string">                &quot;name&quot;:&quot;李四&quot;,</span></span><br><span class="line"><span class="string">                &quot;age&quot;:20</span></span><br><span class="line"><span class="string">            &#125;</span></span><br><span class="line"><span class="string">            &quot;&quot;&quot;</span>.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">    <span class="type">MappingJackson2HttpMessageConverter</span> <span class="variable">converter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>();</span><br><span class="line">    <span class="keyword">if</span> (converter.canRead(User.class, MediaType.APPLICATION_JSON)) &#123;</span><br><span class="line">        <span class="type">Object</span> <span class="variable">read</span> <span class="operator">=</span> converter.read(User.class, message);</span><br><span class="line">        System.out.println(read);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">User&#123;name=&#x27;李四&#x27;, age=20&#125;</span><br></pre></td></tr></table></figure>

<p>如果添加了多个<code>MessageConverter</code>，会以那种优先呢？</p>
<p>添加方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">user</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>添加测试方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test4</span><span class="params">()</span> <span class="keyword">throws</span> IOException, HttpMediaTypeNotAcceptableException, NoSuchMethodException &#123;</span><br><span class="line">    <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">    <span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line">    <span class="type">ServletWebRequest</span> <span class="variable">webRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request, response);</span><br><span class="line"></span><br><span class="line">    <span class="type">RequestResponseBodyMethodProcessor</span> <span class="variable">processor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RequestResponseBodyMethodProcessor</span>(</span><br><span class="line">            List.of(</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>(), <span class="keyword">new</span> <span class="title class_">MappingJackson2XmlHttpMessageConverter</span>()</span><br><span class="line">            ));</span><br><span class="line">    processor.handleReturnValue(</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;张三&quot;</span>, <span class="number">18</span>),</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">MethodParameter</span>(A28.class.getMethod(<span class="string">&quot;user&quot;</span>), -<span class="number">1</span>),</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">ModelAndViewContainer</span>(),</span><br><span class="line">            webRequest</span><br><span class="line">    );</span><br><span class="line">    System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(response.getContentAsByteArray(), StandardCharsets.UTF_8));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;name&quot;:&quot;张三&quot;,&quot;age&quot;:18&#125;</span><br></pre></td></tr></table></figure>

<p>在默认情况下，按照<code>MessageConverter</code>添加的次序进行解析。</p>
<p>如果将<code>MappingJackson2XmlHttpMessageConverter</code>放在前面：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">RequestResponseBodyMethodProcessor</span> <span class="variable">processor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RequestResponseBodyMethodProcessor</span>(</span><br><span class="line">        List.of(</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">MappingJackson2XmlHttpMessageConverter</span>(), <span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>()</span><br><span class="line">        ));</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;User&gt;&lt;name&gt;张三&lt;/name&gt;&lt;age&gt;18&lt;/age&gt;&lt;/User&gt;</span><br></pre></td></tr></table></figure>

<p>如果在请求头中添加可接受的格式，例如：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">request.addHeader(<span class="string">&quot;Accept&quot;</span>, <span class="string">&quot;application/xml&quot;</span>);</span><br></pre></td></tr></table></figure>

<p>那么不论<code>MessageConverter</code>的次序如何，都会返回<code>xml</code>格式的数据。</p>
<p>如果在响应头中设置了<code>ContentType</code>格式，那么按照<code>ContentType</code>设置的格式返回，其优先级比请求头中的<code>Accept</code>高，例如：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">response.setContentType(<span class="string">&quot;application/json&quot;</span>);</span><br></pre></td></tr></table></figure>

<p>此时会以<code>json</code>格式的数据返回。</p>
<p>返回值处理器如何选择<code>MediaType</code>：</p>
<ol>
<li>首先看<code>@RequestMapping</code>上有没有指定(这种情况由于没有环境没有进行测试)</li>
<li>其次看<code>request</code>的<code>Accept</code>头有没有指定</li>
<li>最后按<code>MessageConverter</code>的顺序, 谁能谁先转换</li>
</ol>
<h1 id="ResponseBodyAdvice"><a href="#ResponseBodyAdvice" class="headerlink" title="ResponseBodyAdvice"></a>ResponseBodyAdvice</h1><p>一般在编写<code>controller</code>时，我们都会统一将返回值封装成一个<code>Result</code>对象，包含状态码，消息，数据等。例如：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@JsonInclude(JsonInclude.Include.NON_NULL)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Result</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> code;</span><br><span class="line">    <span class="keyword">private</span> String msg;</span><br><span class="line">    <span class="keyword">private</span> Object data;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> code;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setCode</span><span class="params">(<span class="type">int</span> code)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getMsg</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> msg;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setMsg</span><span class="params">(String msg)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.msg = msg;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">getData</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> data;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setData</span><span class="params">(Object data)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.data = data;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@JsonCreator</span></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">Result</span><span class="params">(<span class="meta">@JsonProperty(&quot;code&quot;)</span> <span class="type">int</span> code, <span class="meta">@JsonProperty(&quot;data&quot;)</span> Object data)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">        <span class="built_in">this</span>.data = data;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">Result</span><span class="params">(<span class="type">int</span> code, String msg)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">        <span class="built_in">this</span>.msg = msg;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Result <span class="title function_">ok</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Result</span>(<span class="number">200</span>, <span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Result <span class="title function_">ok</span><span class="params">(Object data)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Result</span>(<span class="number">200</span>, data);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Result <span class="title function_">error</span><span class="params">(String msg)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Result</span>(<span class="number">500</span>, <span class="string">&quot;服务器内部错误:&quot;</span> + msg);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这种约定的返回方式非常常用，当然也有其他方式可以实现，将返回值封装为一个<code>Result</code>对象。我们可以使用<code>@ControllerAdvice</code>注解对响应体进行增强。</p>
<p>对返回值的增强需要实现<code>ResponseBodyAdvice</code>接口，其中有两个方法：</p>
<ol>
<li><code>supports()</code>：判断是否需要增强返回值</li>
<li><code>beforeBodyWrite()</code>：增强返回值的具体逻辑</li>
</ol>
<p>编写配置类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ControllerAdvice</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyControllerAdvice</span> <span class="keyword">implements</span> <span class="title class_">ResponseBodyAdvice</span>&lt;Object&gt; &#123;</span><br><span class="line">        <span class="comment">// 满足条件才转换 </span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">supports</span><span class="params">(MethodParameter returnType, Class&lt;? extends HttpMessageConverter&lt;?&gt;&gt; converterType)</span> &#123;</span><br><span class="line">            <span class="comment">// 判断方法上的注解||判断类上的注解以及注解中包含的注解</span></span><br><span class="line">            <span class="keyword">if</span> (returnType.getMethodAnnotation(ResponseBody.class) != <span class="literal">null</span> ||</span><br><span class="line">                AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 将 User 或其它类型统一为 Result 类型</span></span><br><span class="line">        <span class="keyword">public</span> Object <span class="title function_">beforeBodyWrite</span><span class="params">(Object body, MethodParameter returnType, MediaType selectedContentType, Class&lt;? extends HttpMessageConverter&lt;?&gt;&gt; selectedConverterType, ServerHttpRequest request, ServerHttpResponse response)</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (body <span class="keyword">instanceof</span> Result) &#123;</span><br><span class="line">                <span class="keyword">return</span> body;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> Result.ok(body);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// @Controller</span></span><br><span class="line">    <span class="comment">// @ResponseBody</span></span><br><span class="line">    <span class="meta">@RestController</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyController</span> &#123;</span><br><span class="line">        <span class="keyword">public</span> User <span class="title function_">user</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;王五&quot;</span>, <span class="number">18</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">User</span><span class="params">(String name, <span class="type">int</span> age)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">            <span class="built_in">this</span>.age = age;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getAge</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> age;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAge</span><span class="params">(<span class="type">int</span> age)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.age = age;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>编写启动类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A29</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">AnnotationConfigApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span></span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">AnnotationConfigApplicationContext</span>(WebConfig.class);</span><br><span class="line"></span><br><span class="line">        <span class="type">ServletInvocableHandlerMethod</span> <span class="variable">handlerMethod</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletInvocableHandlerMethod</span>(</span><br><span class="line">                context.getBean(WebConfig.MyController.class),</span><br><span class="line">                WebConfig.MyController.class.getMethod(<span class="string">&quot;user&quot;</span>)</span><br><span class="line">        );</span><br><span class="line">        handlerMethod.setDataBinderFactory(<span class="keyword">new</span> <span class="title class_">ServletRequestDataBinderFactory</span>(Collections.emptyList(), <span class="literal">null</span>));</span><br><span class="line">        handlerMethod.setParameterNameDiscoverer(<span class="keyword">new</span> <span class="title class_">DefaultParameterNameDiscoverer</span>());</span><br><span class="line">        handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));</span><br><span class="line">        handlerMethod.setHandlerMethodReturnValueHandlers(getReturnValueHandlers(context));</span><br><span class="line"></span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        <span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line">        <span class="type">ModelAndViewContainer</span> <span class="variable">container</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ModelAndViewContainer</span>();</span><br><span class="line">        handlerMethod.invokeAndHandle(<span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request, response), container);</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(response.getContentAsByteArray(), StandardCharsets.UTF_8));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> HandlerMethodArgumentResolverComposite <span class="title function_">getArgumentResolvers</span><span class="params">(AnnotationConfigApplicationContext context)</span> &#123;</span><br><span class="line">        <span class="type">HandlerMethodArgumentResolverComposite</span> <span class="variable">composite</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethodArgumentResolverComposite</span>();</span><br><span class="line">        composite.addResolvers(</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">RequestParamMethodArgumentResolver</span>(context.getDefaultListableBeanFactory(), <span class="literal">false</span>),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">PathVariableMethodArgumentResolver</span>(),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">RequestHeaderMethodArgumentResolver</span>(context.getDefaultListableBeanFactory()),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ServletCookieValueMethodArgumentResolver</span>(context.getDefaultListableBeanFactory()),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ExpressionValueMethodArgumentResolver</span>(context.getDefaultListableBeanFactory()),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ServletRequestMethodArgumentResolver</span>(),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">false</span>),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">RequestResponseBodyMethodProcessor</span>(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>())),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">true</span>),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">RequestParamMethodArgumentResolver</span>(context.getDefaultListableBeanFactory(), <span class="literal">true</span>)</span><br><span class="line">        );</span><br><span class="line">        <span class="keyword">return</span> composite;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> HandlerMethodReturnValueHandlerComposite <span class="title function_">getReturnValueHandlers</span><span class="params">(AnnotationConfigApplicationContext context)</span> &#123;</span><br><span class="line">        <span class="comment">// 添加 advice</span></span><br><span class="line">        List&lt;ControllerAdviceBean&gt; annotatedBeans = ControllerAdviceBean.findAnnotatedBeans(context);</span><br><span class="line">        List&lt;Object&gt; collect = annotatedBeans.stream().filter(b -&gt; ResponseBodyAdvice.class.isAssignableFrom(b.getBeanType()))</span><br><span class="line">                .collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line">        <span class="type">HandlerMethodReturnValueHandlerComposite</span> <span class="variable">composite</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethodReturnValueHandlerComposite</span>();</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">ModelAndViewMethodReturnValueHandler</span>());</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">ViewNameMethodReturnValueHandler</span>());</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">false</span>));</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">HttpEntityMethodProcessor</span>(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>())));</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">HttpHeadersReturnValueHandler</span>());</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">RequestResponseBodyMethodProcessor</span>(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>()), collect));</span><br><span class="line">        composite.addHandler(<span class="keyword">new</span> <span class="title class_">ServletModelAttributeMethodProcessor</span>(<span class="literal">true</span>));</span><br><span class="line">        <span class="keyword">return</span> composite;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;code&quot;:200,&quot;data&quot;:&#123;&quot;name&quot;:&quot;王五&quot;,&quot;age&quot;:18&#125;&#125;</span><br></pre></td></tr></table></figure>

<p>发现返回值的格式已经被重新封装。</p>
<h1 id="异常解析器"><a href="#异常解析器" class="headerlink" title="异常解析器"></a><strong>异常解析器</strong></h1><p><code>DispatcherServlet</code>的核心流程在<code>doDispatch()</code>方法内：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doDispatch</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="type">HttpServletRequest</span> <span class="variable">processedRequest</span> <span class="operator">=</span> request;</span><br><span class="line">    <span class="type">HandlerExecutionChain</span> <span class="variable">mappedHandler</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">multipartRequestParsed</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="type">WebAsyncManager</span> <span class="variable">asyncManager</span> <span class="operator">=</span> WebAsyncUtils.getAsyncManager(request);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">ModelAndView</span> <span class="variable">mv</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">            <span class="type">Exception</span> <span class="variable">dispatchException</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                processedRequest = <span class="built_in">this</span>.checkMultipart(request);</span><br><span class="line">                multipartRequestParsed = processedRequest != request;</span><br><span class="line">                mappedHandler = <span class="built_in">this</span>.getHandler(processedRequest);</span><br><span class="line">                <span class="keyword">if</span> (mappedHandler == <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="built_in">this</span>.noHandlerFound(processedRequest, response);</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="type">HandlerAdapter</span> <span class="variable">ha</span> <span class="operator">=</span> <span class="built_in">this</span>.getHandlerAdapter(mappedHandler.getHandler());</span><br><span class="line">                <span class="type">String</span> <span class="variable">method</span> <span class="operator">=</span> request.getMethod();</span><br><span class="line">                <span class="type">boolean</span> <span class="variable">isGet</span> <span class="operator">=</span> HttpMethod.GET.matches(method);</span><br><span class="line">                <span class="keyword">if</span> (isGet || HttpMethod.HEAD.matches(method)) &#123;</span><br><span class="line">                    <span class="type">long</span> <span class="variable">lastModified</span> <span class="operator">=</span> ha.getLastModified(request, mappedHandler.getHandler());</span><br><span class="line">                    <span class="keyword">if</span> ((<span class="keyword">new</span> <span class="title class_">ServletWebRequest</span>(request, response)).checkNotModified(lastModified) &amp;&amp; isGet) &#123;</span><br><span class="line">                        <span class="keyword">return</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (!mappedHandler.applyPreHandle(processedRequest, response)) &#123;</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());</span><br><span class="line">                <span class="keyword">if</span> (asyncManager.isConcurrentHandlingStarted()) &#123;</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="built_in">this</span>.applyDefaultViewName(processedRequest, mv);</span><br><span class="line">                mappedHandler.applyPostHandle(processedRequest, response, mv);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception var20) &#123;</span><br><span class="line">                dispatchException = var20;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Throwable var21) &#123;</span><br><span class="line">                dispatchException = <span class="keyword">new</span> <span class="title class_">NestedServletException</span>(<span class="string">&quot;Handler dispatch failed&quot;</span>, var21);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="built_in">this</span>.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception var22) &#123;</span><br><span class="line">            <span class="built_in">this</span>.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable var23) &#123;</span><br><span class="line">            <span class="built_in">this</span>.triggerAfterCompletion(processedRequest, response, mappedHandler, <span class="keyword">new</span> <span class="title class_">NestedServletException</span>(<span class="string">&quot;Handler processing failed&quot;</span>, var23));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (asyncManager.isConcurrentHandlingStarted()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (mappedHandler != <span class="literal">null</span>) &#123;</span><br><span class="line">                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (multipartRequestParsed) &#123;</span><br><span class="line">            <span class="built_in">this</span>.cleanupMultipart(processedRequest);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在发生异常时，只是将异常记录在<code>dispatchException</code>变量内，并不直接抛出，最后在<code>processDispatchResult()</code>内进行处理。</p>
<p>我们查看<code>processDispatchResult()</code>方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">processDispatchResult</span><span class="params">(HttpServletRequest request, HttpServletResponse response, <span class="meta">@Nullable</span> HandlerExecutionChain mappedHandler, <span class="meta">@Nullable</span> ModelAndView mv, <span class="meta">@Nullable</span> Exception exception)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">errorView</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">if</span> (exception != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (exception <span class="keyword">instanceof</span> ModelAndViewDefiningException) &#123;</span><br><span class="line">            <span class="built_in">this</span>.logger.debug(<span class="string">&quot;ModelAndViewDefiningException encountered&quot;</span>, exception);</span><br><span class="line">            mv = ((ModelAndViewDefiningException)exception).getModelAndView();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">Object</span> <span class="variable">handler</span> <span class="operator">=</span> mappedHandler != <span class="literal">null</span> ? mappedHandler.getHandler() : <span class="literal">null</span>;</span><br><span class="line">            mv = <span class="built_in">this</span>.processHandlerException(request, response, handler, exception);</span><br><span class="line">            errorView = mv != <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (mv != <span class="literal">null</span> &amp;&amp; !mv.wasCleared()) &#123;</span><br><span class="line">        <span class="built_in">this</span>.render(mv, request, response);</span><br><span class="line">        <span class="keyword">if</span> (errorView) &#123;</span><br><span class="line">            WebUtils.clearErrorRequestAttributes(request);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">this</span>.logger.isTraceEnabled()) &#123;</span><br><span class="line">        <span class="built_in">this</span>.logger.trace(<span class="string">&quot;No view rendering, null ModelAndView returned.&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) &#123;</span><br><span class="line">        <span class="keyword">if</span> (mappedHandler != <span class="literal">null</span>) &#123;</span><br><span class="line">            mappedHandler.triggerAfterCompletion(request, response, (Exception)<span class="literal">null</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>processDispatchResult()</code>方法首先判断异常是否为空，如果不为空则处理异常，否则进行正常的视图渲染流程。</p>
<p>我们进入<code>processHandlerException()</code>方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Nullable</span></span><br><span class="line"><span class="keyword">protected</span> ModelAndView <span class="title function_">processHandlerException</span><span class="params">(HttpServletRequest request, HttpServletResponse response, <span class="meta">@Nullable</span> Object handler, Exception ex)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);</span><br><span class="line">    <span class="type">ModelAndView</span> <span class="variable">exMv</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.handlerExceptionResolvers != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">Iterator</span> <span class="variable">var6</span> <span class="operator">=</span> <span class="built_in">this</span>.handlerExceptionResolvers.iterator();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span>(var6.hasNext()) &#123;</span><br><span class="line">            <span class="type">HandlerExceptionResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> (HandlerExceptionResolver)var6.next();</span><br><span class="line">            exMv = resolver.resolveException(request, response, handler, ex);</span><br><span class="line">            <span class="keyword">if</span> (exMv != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (exMv != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (exMv.isEmpty()) &#123;</span><br><span class="line">            request.setAttribute(EXCEPTION_ATTRIBUTE, ex);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (!exMv.hasView()) &#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">defaultViewName</span> <span class="operator">=</span> <span class="built_in">this</span>.getDefaultViewName(request);</span><br><span class="line">                <span class="keyword">if</span> (defaultViewName != <span class="literal">null</span>) &#123;</span><br><span class="line">                    exMv.setViewName(defaultViewName);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">this</span>.logger.isTraceEnabled()) &#123;</span><br><span class="line">                <span class="built_in">this</span>.logger.trace(<span class="string">&quot;Using resolved error view: &quot;</span> + exMv, ex);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">this</span>.logger.isDebugEnabled()) &#123;</span><br><span class="line">                <span class="built_in">this</span>.logger.debug(<span class="string">&quot;Using resolved error view: &quot;</span> + exMv);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            WebUtils.exposeErrorRequestAttributes(request, ex, <span class="built_in">this</span>.getServletName());</span><br><span class="line">            <span class="keyword">return</span> exMv;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">throw</span> ex;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>processHandlerException()</code>方法遍历<code>handlerExceptionResolvers</code>，查找合适的异常处理器进行处理。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Nullable</span></span><br><span class="line"><span class="keyword">private</span> List&lt;HandlerExceptionResolver&gt; handlerExceptionResolvers;</span><br></pre></td></tr></table></figure>

<p><code>HandlerExceptionResolver</code>接口有一个重要的实现——<code>ExceptionHandlerExceptionResolver</code>。这个解析器就是用来解析<code>@ExceptionHandler</code>注解的。</p>
<ol>
<li>测试处理<code>json</code>返回值</li>
</ol>
<p>编写示例代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A30</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> NoSuchMethodException &#123;</span><br><span class="line">        <span class="type">ExceptionHandlerExceptionResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ExceptionHandlerExceptionResolver</span>();</span><br><span class="line">        resolver.setMessageConverters(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>()));</span><br><span class="line">        resolver.afterPropertiesSet();</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        <span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line">        <span class="comment">// 1.测试 json</span></span><br><span class="line">        <span class="type">HandlerMethod</span> <span class="variable">handlerMethod</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(<span class="keyword">new</span> <span class="title class_">Controller1</span>(), Controller1.class.getMethod(<span class="string">&quot;foo&quot;</span>));</span><br><span class="line">        <span class="type">Exception</span> <span class="variable">e</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArithmeticException</span>(<span class="string">&quot;被零除&quot;</span>);</span><br><span class="line">        resolver.resolveException(request, response, handlerMethod, e);</span><br><span class="line">        System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(response.getContentAsByteArray(), StandardCharsets.UTF_8));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller1</span> &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">foo</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="meta">@ExceptionHandler</span></span><br><span class="line">        <span class="meta">@ResponseBody</span></span><br><span class="line">        <span class="keyword">public</span> Map&lt;String, Object&gt; <span class="title function_">handle</span><span class="params">(ArithmeticException e)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> Map.of(<span class="string">&quot;error&quot;</span>, e.getMessage());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;error&quot;:&quot;被零除&quot;&#125;</span><br></pre></td></tr></table></figure>

<p>发现能正确处理<code>json</code>格式的返回值。</p>
<ol start="2">
<li>测试处理<code>ModelAndView</code>返回值</li>
</ol>
<p>编写示例代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A30</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> NoSuchMethodException &#123;</span><br><span class="line">        <span class="type">ExceptionHandlerExceptionResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ExceptionHandlerExceptionResolver</span>();</span><br><span class="line">        resolver.setMessageConverters(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>()));</span><br><span class="line">        resolver.afterPropertiesSet();</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        <span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line">        <span class="comment">// 2.测试 mav</span></span><br><span class="line">        <span class="type">HandlerMethod</span> <span class="variable">handlerMethod</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(<span class="keyword">new</span> <span class="title class_">Controller2</span>(), Controller2.class.getMethod(<span class="string">&quot;foo&quot;</span>));</span><br><span class="line">        <span class="type">Exception</span> <span class="variable">e</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArithmeticException</span>(<span class="string">&quot;被零除&quot;</span>);</span><br><span class="line">        <span class="type">ModelAndView</span> <span class="variable">mav</span> <span class="operator">=</span> resolver.resolveException(request, response, handlerMethod, e);</span><br><span class="line">        System.out.println(mav.getModel());</span><br><span class="line">        System.out.println(mav.getViewName());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller2</span> &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">foo</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="meta">@ExceptionHandler</span></span><br><span class="line">        <span class="keyword">public</span> ModelAndView <span class="title function_">handle</span><span class="params">(ArithmeticException e)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ModelAndView</span>(<span class="string">&quot;test2&quot;</span>, Map.of(<span class="string">&quot;error&quot;</span>, e.getMessage()));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#123;error=被零除&#125;</span><br><span class="line">test2</span><br></pre></td></tr></table></figure>

<ol start="3">
<li>测试处理嵌套异常的情况</li>
</ol>
<p>编写示例代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A30</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> NoSuchMethodException &#123;</span><br><span class="line">        <span class="type">ExceptionHandlerExceptionResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ExceptionHandlerExceptionResolver</span>();</span><br><span class="line">        resolver.setMessageConverters(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>()));</span><br><span class="line">        resolver.afterPropertiesSet();</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        <span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line">        <span class="comment">// 3.测试嵌套异常</span></span><br><span class="line">        <span class="type">HandlerMethod</span> <span class="variable">handlerMethod</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(<span class="keyword">new</span> <span class="title class_">Controller3</span>(), Controller3.class.getMethod(<span class="string">&quot;foo&quot;</span>));</span><br><span class="line">        <span class="type">Exception</span> <span class="variable">e</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Exception</span>(<span class="string">&quot;e1&quot;</span>, <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;e2&quot;</span>, <span class="keyword">new</span> <span class="title class_">IOException</span>(<span class="string">&quot;e3&quot;</span>)));</span><br><span class="line">        resolver.resolveException(request, response, handlerMethod, e);</span><br><span class="line">        System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(response.getContentAsByteArray(), StandardCharsets.UTF_8));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller3</span> &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">foo</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="meta">@ExceptionHandler</span></span><br><span class="line">        <span class="meta">@ResponseBody</span></span><br><span class="line">        <span class="keyword">public</span> Map&lt;String, Object&gt; <span class="title function_">handle</span><span class="params">(IOException e3)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> Map.of(<span class="string">&quot;error&quot;</span>, e3.getMessage());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;error&quot;:&quot;e3&quot;&#125;</span><br></pre></td></tr></table></figure>

<p>发现这种嵌套异常的情况也能够正确匹配到异常处理器。</p>
<p>查看<code>ExceptionHandlerExceptionResolver</code>类中的<code>doResolveHandlerMethodException()</code>方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//...</span></span><br><span class="line">Throwable cause;</span><br><span class="line"><span class="keyword">for</span>(<span class="type">Throwable</span> <span class="variable">exToExpose</span> <span class="operator">=</span> exception; exToExpose != <span class="literal">null</span>; exToExpose = cause != exToExpose ? cause : <span class="literal">null</span>) &#123;</span><br><span class="line">    exceptions.add(exToExpose);</span><br><span class="line">    cause = ((Throwable)exToExpose).getCause();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//..</span></span><br></pre></td></tr></table></figure>

<p>注意这段逻辑，首先拿到最外层异常，然后循环拿到起因异常，最后将所有异常都添加到<code>exceptions</code>中，因此它能够处理嵌套异常。</p>
<ol start="4">
<li>测试异常处理方法参数解析</li>
</ol>
<p>编写示例代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A30</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> NoSuchMethodException &#123;</span><br><span class="line">        <span class="type">ExceptionHandlerExceptionResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ExceptionHandlerExceptionResolver</span>();</span><br><span class="line">        resolver.setMessageConverters(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>()));</span><br><span class="line">        resolver.afterPropertiesSet();</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        <span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line">        <span class="comment">// 4.测试异常处理方法参数解析</span></span><br><span class="line">        <span class="type">HandlerMethod</span> <span class="variable">handlerMethod</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(<span class="keyword">new</span> <span class="title class_">Controller4</span>(), Controller4.class.getMethod(<span class="string">&quot;foo&quot;</span>));</span><br><span class="line">        <span class="type">Exception</span> <span class="variable">e</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Exception</span>(<span class="string">&quot;e1&quot;</span>);</span><br><span class="line">        resolver.resolveException(request, response, handlerMethod, e);</span><br><span class="line">        System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(response.getContentAsByteArray(), StandardCharsets.UTF_8));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller4</span> &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">foo</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">        <span class="meta">@ExceptionHandler</span></span><br><span class="line">        <span class="meta">@ResponseBody</span></span><br><span class="line">        <span class="keyword">public</span> Map&lt;String, Object&gt; <span class="title function_">handler</span><span class="params">(Exception e, HttpServletRequest request)</span> &#123;</span><br><span class="line">            System.out.println(request);</span><br><span class="line">            <span class="keyword">return</span> Map.of(<span class="string">&quot;error&quot;</span>, e.getMessage());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.mock.web.MockHttpServletRequest@60db1c0e</span><br><span class="line">&#123;&quot;error&quot;:&quot;e1&quot;&#125;</span><br></pre></td></tr></table></figure>

<p>在抛出异常时，也能正确拿到<code>request</code>对象。</p>
<h1 id="ExceptionHandler注解"><a href="#ExceptionHandler注解" class="headerlink" title="@ExceptionHandler注解"></a>@ExceptionHandler注解</h1><p><code>@ControllerAdvice</code>注解配合<code>@ExceptionHandler</code>注解可以实现全局异常处理器，不需要每个<code>Controller</code>中都添加<code>ExceptionHandler</code>。</p>
<p>编写测试代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 配置类</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> &#123;</span><br><span class="line">    <span class="meta">@ControllerAdvice</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyControllerAdvice</span> &#123;</span><br><span class="line">        <span class="meta">@ExceptionHandler</span></span><br><span class="line">        <span class="meta">@ResponseBody</span></span><br><span class="line">        <span class="keyword">public</span> Map&lt;String, Object&gt; <span class="title function_">handle</span><span class="params">(Exception e)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> Map.of(<span class="string">&quot;error&quot;</span>, e.getMessage());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> ExceptionHandlerExceptionResolver <span class="title function_">resolver</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ExceptionHandlerExceptionResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ExceptionHandlerExceptionResolver</span>();</span><br><span class="line">        resolver.setMessageConverters(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>()));</span><br><span class="line">        <span class="keyword">return</span> resolver;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 启动类</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A31</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> NoSuchMethodException &#123;</span><br><span class="line">        <span class="type">MockHttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletRequest</span>();</span><br><span class="line">        <span class="type">MockHttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MockHttpServletResponse</span>();</span><br><span class="line">        <span class="type">AnnotationConfigApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AnnotationConfigApplicationContext</span>(WebConfig.class);</span><br><span class="line">        <span class="type">ExceptionHandlerExceptionResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> context.getBean(ExceptionHandlerExceptionResolver.class);</span><br><span class="line"></span><br><span class="line">        <span class="type">HandlerMethod</span> <span class="variable">handlerMethod</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerMethod</span>(<span class="keyword">new</span> <span class="title class_">Controller5</span>(), Controller5.class.getMethod(<span class="string">&quot;foo&quot;</span>));</span><br><span class="line">        <span class="type">Exception</span> <span class="variable">e</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Exception</span>(<span class="string">&quot;e1&quot;</span>);</span><br><span class="line">        resolver.resolveException(request, response, handlerMethod, e);</span><br><span class="line">        System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(response.getContentAsByteArray(), StandardCharsets.UTF_8));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller5</span> &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">foo</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，查看控制台：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;error&quot;:&quot;e1&quot;&#125;</span><br></pre></td></tr></table></figure>

<p>我们发现在<code>Controller5</code>中没有添加<code>ExceptionHandler</code>，通过全局的<code>ExceptionHandler</code>也能处理异常。</p>
<p>异常处理流程：</p>
<ol>
<li>优先在<code>Controller</code>中找<code>ExceptionHandler</code></li>
<li>其次在全局处理器中找<code>ExceptionHandler</code></li>
</ol>
<p>查看<code>ExceptionHandlerExceptionResolver</code>中的<code>afterPropertiesSet()</code>方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterPropertiesSet</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.initExceptionHandlerAdviceCache();</span><br><span class="line">    List handlers;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.argumentResolvers == <span class="literal">null</span>) &#123;</span><br><span class="line">        handlers = <span class="built_in">this</span>.getDefaultArgumentResolvers();</span><br><span class="line">        <span class="built_in">this</span>.argumentResolvers = (<span class="keyword">new</span> <span class="title class_">HandlerMethodArgumentResolverComposite</span>()).addResolvers(handlers);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.returnValueHandlers == <span class="literal">null</span>) &#123;</span><br><span class="line">        handlers = <span class="built_in">this</span>.getDefaultReturnValueHandlers();</span><br><span class="line">        <span class="built_in">this</span>.returnValueHandlers = (<span class="keyword">new</span> <span class="title class_">HandlerMethodReturnValueHandlerComposite</span>()).addHandlers(handlers);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>initExceptionHandlerAdviceCache()</code>即是处理<code>@ControllerAdvice</code>注解标注的配置，进入此方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">initExceptionHandlerAdviceCache</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.getApplicationContext() != <span class="literal">null</span>) &#123;</span><br><span class="line">        List&lt;ControllerAdviceBean&gt; adviceBeans = ControllerAdviceBean.findAnnotatedBeans(<span class="built_in">this</span>.getApplicationContext());</span><br><span class="line">        <span class="type">Iterator</span> <span class="variable">var2</span> <span class="operator">=</span> adviceBeans.iterator();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span>(var2.hasNext()) &#123;</span><br><span class="line">            <span class="type">ControllerAdviceBean</span> <span class="variable">adviceBean</span> <span class="operator">=</span> (ControllerAdviceBean)var2.next();</span><br><span class="line">            Class&lt;?&gt; beanType = adviceBean.getBeanType();</span><br><span class="line">            <span class="keyword">if</span> (beanType == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Unresolvable type for ControllerAdviceBean: &quot;</span> + adviceBean);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="type">ExceptionHandlerMethodResolver</span> <span class="variable">resolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ExceptionHandlerMethodResolver</span>(beanType);</span><br><span class="line">            <span class="keyword">if</span> (resolver.hasExceptionMappings()) &#123;</span><br><span class="line">                <span class="built_in">this</span>.exceptionHandlerAdviceCache.put(adviceBean, resolver);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (ResponseBodyAdvice.class.isAssignableFrom(beanType)) &#123;</span><br><span class="line">                <span class="built_in">this</span>.responseBodyAdvice.add(adviceBean);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span>.logger.isDebugEnabled()) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">handlerSize</span> <span class="operator">=</span> <span class="built_in">this</span>.exceptionHandlerAdviceCache.size();</span><br><span class="line">            <span class="type">int</span> <span class="variable">adviceSize</span> <span class="operator">=</span> <span class="built_in">this</span>.responseBodyAdvice.size();</span><br><span class="line">            <span class="keyword">if</span> (handlerSize == <span class="number">0</span> &amp;&amp; adviceSize == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="built_in">this</span>.logger.debug(<span class="string">&quot;ControllerAdvice beans: none&quot;</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="built_in">this</span>.logger.debug(<span class="string">&quot;ControllerAdvice beans: &quot;</span> + handlerSize + <span class="string">&quot; @ExceptionHandler, &quot;</span> + adviceSize + <span class="string">&quot; ResponseBodyAdvice&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>此方法中获取并添加了<code>ControllerAdvice</code>中的<code>ExceptionHandler</code>以及<code>ResponseBody</code>增强。</p>
<h1 id="Tomcat异常处理"><a href="#Tomcat异常处理" class="headerlink" title="Tomcat异常处理"></a><strong>Tomcat异常处理</strong></h1><p><code>ControllerAdvice</code>中的<code>ExceptionHandler</code>只能处理<code>Controller</code>中抛出的异常，如果是过滤器中抛出的异常，那么<code>ControllerAdvice</code>不能处理，因此需要更上一级的异常处理。</p>
<p>测试<code>tomcat</code>中默认的异常处理：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 配置类</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> TomcatServletWebServerFactory <span class="title function_">servletWebServerFactory</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">TomcatServletWebServerFactory</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DispatcherServlet <span class="title function_">dispatcherServlet</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DispatcherServlet</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DispatcherServletRegistrationBean <span class="title function_">servletRegistrationBean</span><span class="params">(DispatcherServlet dispatcherServlet)</span> &#123;</span><br><span class="line">        <span class="type">DispatcherServletRegistrationBean</span> <span class="variable">registrationBean</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DispatcherServletRegistrationBean</span>(dispatcherServlet, <span class="string">&quot;/&quot;</span>);</span><br><span class="line">        registrationBean.setLoadOnStartup(<span class="number">1</span>);</span><br><span class="line">        <span class="keyword">return</span> registrationBean;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span> <span class="comment">// @RequestMapping</span></span><br><span class="line">    <span class="keyword">public</span> RequestMappingHandlerMapping <span class="title function_">requestMappingHandlerMapping</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RequestMappingHandlerMapping</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span> <span class="comment">// 注意默认的 RequestMappingHandlerAdapter 不会带 jackson 转换器</span></span><br><span class="line">    <span class="keyword">public</span> RequestMappingHandlerAdapter <span class="title function_">requestMappingHandlerAdapter</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">RequestMappingHandlerAdapter</span> <span class="variable">handlerAdapter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RequestMappingHandlerAdapter</span>();</span><br><span class="line">        handlerAdapter.setMessageConverters(List.of(<span class="keyword">new</span> <span class="title class_">MappingJackson2HttpMessageConverter</span>()));</span><br><span class="line">        <span class="keyword">return</span> handlerAdapter;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> ErrorPageRegistrarBeanPostProcessor <span class="title function_">errorPageRegistrarBeanPostProcessor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ErrorPageRegistrarBeanPostProcessor</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Controller</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyController</span> &#123;</span><br><span class="line">        <span class="meta">@RequestMapping(&quot;test&quot;)</span></span><br><span class="line">        <span class="keyword">public</span> ModelAndView <span class="title function_">test</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span> / <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 启动类</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A32</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> NoSuchFieldException, IllegalAccessException &#123;</span><br><span class="line">        <span class="type">AnnotationConfigServletWebServerApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span></span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">AnnotationConfigServletWebServerApplicationContext</span>(WebConfig.class);</span><br><span class="line">        <span class="type">RequestMappingHandlerMapping</span> <span class="variable">handlerMapping</span> <span class="operator">=</span> context.getBean(RequestMappingHandlerMapping.class);</span><br><span class="line">        handlerMapping.getHandlerMethods().forEach((RequestMappingInfo k, HandlerMethod v) -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;映射路径:&quot;</span> + k + <span class="string">&quot;\t方法信息:&quot;</span> + v);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，访问localhost:8080&#x2F;test，浏览器上出现以下页面：</p>
<p><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/8.png" alt="img"></p>
<p>说明<code>tomcat</code>是有默认的异常的异常处理以及跳转逻辑的。</p>
<p>为了提高用户体验，一般来讲需要返回<code>json</code>格式的错误信息，因此我们需要自定义异常处理。</p>
<p>自定义异常处理：</p>
<p>修改<code>Tomcat</code>服务器默认错误转发地址，添加以下<code>Bean</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> ErrorPageRegistrar <span class="title function_">errorPageRegistrar</span><span class="params">()</span> &#123; <span class="comment">// 出现错误，会使用请求转发 forward 跳转到 error 地址</span></span><br><span class="line">    <span class="keyword">return</span> webServerFactory -&gt; webServerFactory.addErrorPages(<span class="keyword">new</span> <span class="title class_">ErrorPage</span>(<span class="string">&quot;/error&quot;</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>添加<code>ErrorPageRegistrarBeanPostProcessor</code>，这个Bean将会回调<code>webServerFactory</code>的<code>addErrorPages()</code>方法，去真正执行修改错误页面的动作：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> ErrorPageRegistrarBeanPostProcessor <span class="title function_">errorPageRegistrarBeanPostProcessor</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ErrorPageRegistrarBeanPostProcessor</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>添加<code>Controller</code>方法，返回<code>json</code>格式的数据：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestMapping(&quot;/error&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> Map&lt;String, Object&gt; <span class="title function_">error</span><span class="params">(HttpServletRequest request)</span> &#123;</span><br><span class="line">    <span class="type">Throwable</span> <span class="variable">e</span> <span class="operator">=</span> (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);</span><br><span class="line">    <span class="keyword">return</span> Map.of(<span class="string">&quot;error&quot;</span>, e.getMessage());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，访问localhost:8080&#x2F;test，浏览器上出现以下输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">&quot;error&quot;: &quot;Request processing failed; nested exception is java.lang.ArithmeticException: / by zero&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>成功将我们的错误信息以<code>json</code>格式返回。</p>
<p>除了通过<code>tomcat</code>提供的方式自定义异常处理，<code>springboot</code>中也提供了<code>BasicErrorController</code>支持我们自定义异常处理。</p>
<p>查看<code>BasicErrorController</code>源码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping(&#123;&quot;$&#123;server.error.path:$&#123;error.path:/error&#125;&#125;&quot;&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BasicErrorController</span> <span class="keyword">extends</span> <span class="title class_">AbstractErrorController</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>发现这也是一个<code>Controller</code>，它的<code>RequestMapping</code>映射的路径寻找规则：</p>
<ol>
<li>首先查找<code>server.error.path</code>值</li>
<li>其次查找<code>error.path</code>值</li>
<li>最后使用默认的<code>/error</code>路径</li>
</ol>
<p>我们在容器中添加<code>BasicErrorController</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> BasicErrorController <span class="title function_">basicErrorController</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="comment">// 第一个参数指的是异常信息，第二个参数指配置文件中的键值</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">BasicErrorController</span>(<span class="keyword">new</span> <span class="title class_">DefaultErrorAttributes</span>(), <span class="keyword">new</span> <span class="title class_">ErrorProperties</span>());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>BasicErrorController</code>异常处理考虑得更加全面，其中有两个RequestMapping：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestMapping(</span></span><br><span class="line"><span class="meta">    produces = &#123;&quot;text/html&quot;&#125;</span></span><br><span class="line"><span class="meta">)</span></span><br><span class="line"><span class="keyword">public</span> ModelAndView <span class="title function_">errorHtml</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> &#123;</span><br><span class="line">    <span class="type">HttpStatus</span> <span class="variable">status</span> <span class="operator">=</span> <span class="built_in">this</span>.getStatus(request);</span><br><span class="line">    Map&lt;String, Object&gt; model = Collections.unmodifiableMap(<span class="built_in">this</span>.getErrorAttributes(request, <span class="built_in">this</span>.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));</span><br><span class="line">    response.setStatus(status.value());</span><br><span class="line">    <span class="type">ModelAndView</span> <span class="variable">modelAndView</span> <span class="operator">=</span> <span class="built_in">this</span>.resolveErrorView(request, response, status, model);</span><br><span class="line">    <span class="keyword">return</span> modelAndView != <span class="literal">null</span> ? modelAndView : <span class="keyword">new</span> <span class="title class_">ModelAndView</span>(<span class="string">&quot;error&quot;</span>, model);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@RequestMapping</span></span><br><span class="line"><span class="keyword">public</span> ResponseEntity&lt;Map&lt;String, Object&gt;&gt; <span class="title function_">error</span><span class="params">(HttpServletRequest request)</span> &#123;</span><br><span class="line">    <span class="type">HttpStatus</span> <span class="variable">status</span> <span class="operator">=</span> <span class="built_in">this</span>.getStatus(request);</span><br><span class="line">    <span class="keyword">if</span> (status == HttpStatus.NO_CONTENT) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResponseEntity</span>(status);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        Map&lt;String, Object&gt; body = <span class="built_in">this</span>.getErrorAttributes(request, <span class="built_in">this</span>.getErrorAttributeOptions(request, MediaType.ALL));</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResponseEntity</span>(body, status);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>根据前端想要的返回格式，一种是返回<code>html</code>格式，另一种是<code>json</code>格式。</p>
<p>运行启动类，使用<code>postman</code>进行测试：</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="number">1680758526029</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">500</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Internal Server Error&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/test&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>返回的一串<code>json</code>格式的数据，其中的属性就是我们构造<code>BasicErrorController</code>时，传入的<code>DefaultErrorAttributes</code>。</p>
<p>查看<code>DefaultErrorAttributes</code>的<code>getErrorAttributes()</code>方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Map&lt;String, Object&gt; <span class="title function_">getErrorAttributes</span><span class="params">(WebRequest webRequest, <span class="type">boolean</span> includeStackTrace)</span> &#123;</span><br><span class="line">    Map&lt;String, Object&gt; errorAttributes = <span class="keyword">new</span> <span class="title class_">LinkedHashMap</span>();</span><br><span class="line">    errorAttributes.put(<span class="string">&quot;timestamp&quot;</span>, <span class="keyword">new</span> <span class="title class_">Date</span>());</span><br><span class="line">    <span class="built_in">this</span>.addStatus(errorAttributes, webRequest);</span><br><span class="line">    <span class="built_in">this</span>.addErrorDetails(errorAttributes, webRequest, includeStackTrace);</span><br><span class="line">    <span class="built_in">this</span>.addPath(errorAttributes, webRequest);</span><br><span class="line">    <span class="keyword">return</span> errorAttributes;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们发现返回的<code>json</code>数据中没有真正的错误信息，展示错误信息有两种方法：</p>
<ol>
<li>可以通过在配置文件中添加<code>server.error.include-exception=true</code>配置</li>
<li>可以在构造<code>BasicErrorController</code>时传入配置：</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> BasicErrorController <span class="title function_">basicErrorController</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="type">ErrorProperties</span> <span class="variable">errorProperties</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ErrorProperties</span>();</span><br><span class="line">  errorProperties.setIncludeException(<span class="literal">true</span>);</span><br><span class="line">  <span class="comment">// 第一个参数指的是异常信息，第二个参数指配置文件中的键值</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">BasicErrorController</span>(<span class="keyword">new</span> <span class="title class_">DefaultErrorAttributes</span>(), errorProperties);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，使用<code>postman</code>进行测试：</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="number">1680758791767</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="number">500</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Internal Server Error&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;exception&quot;</span><span class="punctuation">:</span> <span class="string">&quot;java.lang.ArithmeticException&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/test&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>已经成功添加了错误信息。</p>
<p>我们通过浏览器访问的方式测试<code>BasicErrorController</code>：</p>
<p><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/9.png" alt="img"></p>
<p>发现还是使用的是<code>tomcat</code>异常处理的方式，我们查看<code>BasicErrorController</code>源码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestMapping(</span></span><br><span class="line"><span class="meta">    produces = &#123;&quot;text/html&quot;&#125;</span></span><br><span class="line"><span class="meta">)</span></span><br><span class="line"><span class="keyword">public</span> ModelAndView <span class="title function_">errorHtml</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> &#123;</span><br><span class="line">    <span class="type">HttpStatus</span> <span class="variable">status</span> <span class="operator">=</span> <span class="built_in">this</span>.getStatus(request);</span><br><span class="line">    Map&lt;String, Object&gt; model = Collections.unmodifiableMap(<span class="built_in">this</span>.getErrorAttributes(request, <span class="built_in">this</span>.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));</span><br><span class="line">    response.setStatus(status.value());</span><br><span class="line">    <span class="type">ModelAndView</span> <span class="variable">modelAndView</span> <span class="operator">=</span> <span class="built_in">this</span>.resolveErrorView(request, response, status, model);</span><br><span class="line">    <span class="keyword">return</span> modelAndView != <span class="literal">null</span> ? modelAndView : <span class="keyword">new</span> <span class="title class_">ModelAndView</span>(<span class="string">&quot;error&quot;</span>, model);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>发现最终返回的是一个<code>ModelAndView</code>对象，但此时我们没有添加名为<code>error</code>的视图，因此没有生效。</p>
<p>添加<code>error</code>视图以及视图解析器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 方法名称即为视图名称</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> View <span class="title function_">error</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">View</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">render</span><span class="params">(Map&lt;String, ?&gt; model, HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">            System.out.println(model);</span><br><span class="line">            response.setContentType(<span class="string">&quot;text/html;charset=utf-8&quot;</span>);</span><br><span class="line">            response.getWriter().print(<span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">                    &lt;h3&gt;服务器内部错误&lt;/h3&gt;</span></span><br><span class="line"><span class="string">                    &quot;&quot;&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 视图解析器</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> ViewResolver <span class="title function_">viewResolver</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">BeanNameViewResolver</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，访问localhost:8080&#x2F;test，浏览器上出现以下页面：</p>
<p><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/10.png" alt="img"></p>
<p>已经能正确解析我们的自定义视图。</p>
<p><code>springboot</code>中其实就是采用的<code>BasicErrorController</code>来做错误处理。</p>
<h1 id="BeanNameUrlHandlerMapping与SimpleControllerHandlerAdapter"><a href="#BeanNameUrlHandlerMapping与SimpleControllerHandlerAdapter" class="headerlink" title="BeanNameUrlHandlerMapping与SimpleControllerHandlerAdapter"></a>BeanNameUrlHandlerMapping与SimpleControllerHandlerAdapter</h1><p><code>BeanNameUrlHandlerMapping</code>与<code>SimpleControllerHandlerAdapter</code>是<code>SpringMVC</code>早期的实现，比<code>RequestMappingHandlerMapping</code>和<code>RequestMappingHandlerAdapter</code>的实现更加简单。</p>
<p><strong>BeanNameUrlHandlerMapping</strong></p>
<p><code>BeanNameUrlHandlerMapping</code>的作用是通过请求路径去查找相应的控制器的名称，例如我们访问<code>/test</code>路径，<code>BeanNameUrlHandlerMapping</code>就回去查找容器中名为<code>/test</code>的<code>Bean</code>，以这种规则去做路径与控制器的映射。</p>
<p>以下这三个<code>Bean</code>分别映射路径<code>/c1</code>、<code>/c2</code>、<code>/c3</code>。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component(&quot;/c1&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller1</span> <span class="keyword">implements</span> <span class="title class_">Controller</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">handleRequest</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        response.getWriter().print(<span class="string">&quot;this is c1&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component(&quot;/c2&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller2</span> <span class="keyword">implements</span> <span class="title class_">Controller</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">handleRequest</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        response.getWriter().print(<span class="string">&quot;this is c2&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean(&quot;/c3&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Controller <span class="title function_">controller3</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> (request, response) -&gt; &#123;</span><br><span class="line">        response.getWriter().print(<span class="string">&quot;this is c3&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>SimpleControllerHandlerAdapter</strong></p>
<p><code>SimpleControllerHandlerAdapter</code>的作用是调用控制器的方法，它要求每个控制器必须实现<code>Controller</code>，重写<code>handleRequest()</code>方法才能调用。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component(&quot;/c1&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller1</span> <span class="keyword">implements</span> <span class="title class_">Controller</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">handleRequest</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        response.getWriter().print(<span class="string">&quot;this is c1&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component(&quot;/c2&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Controller2</span> <span class="keyword">implements</span> <span class="title class_">Controller</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">handleRequest</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        response.getWriter().print(<span class="string">&quot;this is c2&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean(&quot;/c3&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Controller <span class="title function_">controller3</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> (request, response) -&gt; &#123;</span><br><span class="line">        response.getWriter().print(<span class="string">&quot;this is c3&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>添加启动类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A33</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">AnnotationConfigServletWebServerApplicationContext</span> <span class="variable">context</span></span><br><span class="line">                <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AnnotationConfigServletWebServerApplicationContext</span>(WebConfig.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，访问<a target="_blank" rel="noopener" href="http://localhost:8080/c1%EF%BC%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E8%BE%93%E5%87%BA%EF%BC%9A">http://localhost:8080/c1，浏览器输出：</a></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">this is c1</span><br></pre></td></tr></table></figure>

<p>访问<a target="_blank" rel="noopener" href="http://localhost:8080/c2%EF%BC%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E8%BE%93%E5%87%BA%EF%BC%9A">http://localhost:8080/c2，浏览器输出：</a></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">this is c2</span><br></pre></td></tr></table></figure>

<p>访问<a target="_blank" rel="noopener" href="http://localhost:8080/c3%EF%BC%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E8%BE%93%E5%87%BA%EF%BC%9A">http://localhost:8080/c3，浏览器输出：</a></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">this is c3</span><br></pre></td></tr></table></figure>

<p>都能映射到正确的控制器中。</p>
<h1 id="自定义HandlerMapping与HandlerAdapter"><a href="#自定义HandlerMapping与HandlerAdapter" class="headerlink" title="自定义HandlerMapping与HandlerAdapter"></a>自定义HandlerMapping与HandlerAdapter</h1><p>我们自定义<code>HandlerMapping</code>与<code>HandlerAdapter</code>实现简单的<code>BeanNameUrlHandlerMapping</code>与<code>SimpleControllerHandlerAdapter</code>。</p>
<p><code>HandlerMapping</code>的实现：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyHandlerMapping</span> <span class="keyword">implements</span> <span class="title class_">HandlerMapping</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> HandlerExecutionChain <span class="title function_">getHandler</span><span class="params">(HttpServletRequest request)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> request.getRequestURI();</span><br><span class="line">        <span class="type">Controller</span> <span class="variable">controller</span> <span class="operator">=</span> collect.get(key);</span><br><span class="line">        <span class="keyword">if</span> (controller == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">HandlerExecutionChain</span>(controller);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ApplicationContext context;</span><br><span class="line">    <span class="keyword">private</span> Map&lt;String, Controller&gt; collect;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        collect = context.getBeansOfType(Controller.class).entrySet()</span><br><span class="line">                .stream().filter(e -&gt; e.getKey().startsWith(<span class="string">&quot;/&quot;</span>))</span><br><span class="line">                .collect(Collectors.toMap(e -&gt; e.getKey(), e -&gt; e.getValue()));</span><br><span class="line">        System.out.println(collect);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>实现步骤：</p>
<ol>
<li>在初始化方法中获取所有的<code>Bean</code>，并过滤不以<code>&quot;/&quot;</code>开头的名称</li>
<li>在映射时，根据请求路径获取<code>Bean</code>，并封装为<code>HandlerExecutionChain</code>。</li>
</ol>
<p><code>HandlerAdapter</code>的实现：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyHandlerAdapter</span> <span class="keyword">implements</span> <span class="title class_">HandlerAdapter</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">supports</span><span class="params">(Object handler)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> handler <span class="keyword">instanceof</span> Controller;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> ModelAndView <span class="title function_">handle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="keyword">if</span> (handler <span class="keyword">instanceof</span> Controller controller) &#123;</span><br><span class="line">            controller.handleRequest(request, response);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getLastModified</span><span class="params">(HttpServletRequest request, Object handler)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>HandlerAdapter</code>接口有三个方法需要实现：</p>
<ol>
<li><code>supports()</code>：是否支持<code>Controller</code>调用</li>
<li><code>handle()</code>：调用方法的具体逻辑</li>
<li><code>getLastModified()</code>：处理修改时间，缓存相关</li>
</ol>
<p>编写启动类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A33_1</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">AnnotationConfigServletWebServerApplicationContext</span> <span class="variable">context</span></span><br><span class="line">                <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AnnotationConfigServletWebServerApplicationContext</span>(WebConfig_1.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，访问<a target="_blank" rel="noopener" href="http://localhost:8080/c1%EF%BC%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E8%BE%93%E5%87%BA%EF%BC%9A">http://localhost:8080/c1，浏览器输出：</a></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">this is c1</span><br></pre></td></tr></table></figure>

<h1 id="RouterFunctionMapping与HandlerFunctionAdapter"><a href="#RouterFunctionMapping与HandlerFunctionAdapter" class="headerlink" title="RouterFunctionMapping与HandlerFunctionAdapter"></a>RouterFunctionMapping与HandlerFunctionAdapter</h1><p><code>RouterFunctionMapping</code>与<code>HandlerFunctionAdapter</code>是一对新的实现，在<code>spring5.2</code>版本引入的。</p>
<ol>
<li><code>RouterFunctionMapping</code>, 收集所有<code>RouterFunction</code>，它包括两部分： <code>RequestPredicate</code>设置映射条件，<code>HandlerFunction</code>包含处理逻辑</li>
<li>请求到达，根据映射条件找到<code>HandlerFunction</code>，即<code>handler</code></li>
<li><code>HandlerFunctionAdapter</code>调用<code>handler</code></li>
</ol>
<p>编写配置类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span> <span class="comment">// ⬅️内嵌 web 容器工厂</span></span><br><span class="line">    <span class="keyword">public</span> TomcatServletWebServerFactory <span class="title function_">servletWebServerFactory</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">TomcatServletWebServerFactory</span>(<span class="number">8080</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span> <span class="comment">// ⬅️创建 DispatcherServlet</span></span><br><span class="line">    <span class="keyword">public</span> DispatcherServlet <span class="title function_">dispatcherServlet</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DispatcherServlet</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span> <span class="comment">// ⬅️注册 DispatcherServlet, Spring MVC 的入口</span></span><br><span class="line">    <span class="keyword">public</span> DispatcherServletRegistrationBean <span class="title function_">servletRegistrationBean</span><span class="params">(DispatcherServlet dispatcherServlet)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DispatcherServletRegistrationBean</span>(dispatcherServlet, <span class="string">&quot;/&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 路径映射</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RouterFunctionMapping <span class="title function_">routerFunctionMapping</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RouterFunctionMapping</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 调用handler</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> HandlerFunctionAdapter <span class="title function_">handlerFunctionAdapter</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">HandlerFunctionAdapter</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RouterFunction&lt;ServerResponse&gt; <span class="title function_">r1</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 参数一为请求的方法和路径，方法二为处理逻辑</span></span><br><span class="line">        <span class="keyword">return</span> route(GET(<span class="string">&quot;/r1&quot;</span>), request -&gt; ok().body(<span class="string">&quot;this is r1&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RouterFunction&lt;ServerResponse&gt; <span class="title function_">r2</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> route(GET(<span class="string">&quot;/r2&quot;</span>), request -&gt; ok().body(<span class="string">&quot;this is r2&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>编写启动类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A34</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">AnnotationConfigServletWebServerApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span></span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">AnnotationConfigServletWebServerApplicationContext</span>(WebConfig.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，通过浏览器访问localhost:8080&#x2F;r1，浏览器输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">this is r1</span><br></pre></td></tr></table></figure>

<p>运行启动类，通过浏览器访问localhost:8080&#x2F;r2，浏览器输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">this is r2</span><br></pre></td></tr></table></figure>

<p>函数式控制器与经典控制器的对比：</p>
<p>函数式控制器：</p>
<ol>
<li><code>RouterFunctionMapping</code>，通过<code>RequestPredicate</code>映射</li>
<li><code>handler</code>要实现<code>HandlerFunction</code>接口</li>
<li><code>HandlerFunctionAdapter</code>，调用<code>handler</code></li>
</ol>
<p>经典控制器：</p>
<ol>
<li><code>RequestMappingHandlerMapping</code>，以<code>@RequestMapping</code>作为映射路径</li>
<li>控制器的具体方法会被当作<code>handler</code></li>
<li><code>RequestMappingHandlerAdapter</code>，调用<code>handler</code></li>
</ol>
<p>函数式控制器的实现没有经典控制器实现功能强大，其特点是比较简洁，适合简单业务场景下使用。</p>
<h1 id="SimpleUrlHandlerMapping与HttpRequestHandlerAdapter"><a href="#SimpleUrlHandlerMapping与HttpRequestHandlerAdapter" class="headerlink" title="SimpleUrlHandlerMapping与HttpRequestHandlerAdapter"></a>SimpleUrlHandlerMapping与HttpRequestHandlerAdapter</h1><p><code>SimpleUrlHandlerMapping</code>与<code>HttpRequestHandlerAdapter</code>主要用于处理静态资源：</p>
<ol>
<li><code>SimpleUrlHandlerMapping</code>做映射</li>
<li><code>ResourceHttpRequestHandler</code>作为处理器处理静态资源</li>
<li><code>HttpRequestHandlerAdapter</code>调用处理器</li>
</ol>
<p>编写配置类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span> <span class="comment">// ⬅️内嵌 web 容器工厂</span></span><br><span class="line">    <span class="keyword">public</span> TomcatServletWebServerFactory <span class="title function_">servletWebServerFactory</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">TomcatServletWebServerFactory</span>(<span class="number">8080</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span> <span class="comment">// ⬅️创建 DispatcherServlet</span></span><br><span class="line">    <span class="keyword">public</span> DispatcherServlet <span class="title function_">dispatcherServlet</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DispatcherServlet</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span> <span class="comment">// ⬅️注册 DispatcherServlet, Spring MVC 的入口</span></span><br><span class="line">    <span class="keyword">public</span> DispatcherServletRegistrationBean <span class="title function_">servletRegistrationBean</span><span class="params">(DispatcherServlet dispatcherServlet)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DispatcherServletRegistrationBean</span>(dispatcherServlet, <span class="string">&quot;/&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 记录映射路径与处理器</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> context</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SimpleUrlHandlerMapping <span class="title function_">simpleUrlHandlerMapping</span><span class="params">(ApplicationContext context)</span> &#123;</span><br><span class="line">        <span class="type">SimpleUrlHandlerMapping</span> <span class="variable">handlerMapping</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleUrlHandlerMapping</span>();</span><br><span class="line">        Map&lt;String, ResourceHttpRequestHandler&gt; map = context.getBeansOfType(ResourceHttpRequestHandler.class);</span><br><span class="line">        handlerMapping.setUrlMap(map);</span><br><span class="line">        System.out.println(map);</span><br><span class="line">        <span class="keyword">return</span> handlerMapping;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> HttpRequestHandlerAdapter <span class="title function_">httpRequestHandlerAdapter</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">HttpRequestHandlerAdapter</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 请求的所有路径都会被映射到资源目录下</span></span><br><span class="line"><span class="comment">     * 例如访问/r1.html、/r2.html、/r3.html</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean(&quot;/**&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ResourceHttpRequestHandler <span class="title function_">handler1</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ResourceHttpRequestHandler</span> <span class="variable">handler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ResourceHttpRequestHandler</span>();</span><br><span class="line">        <span class="comment">// 设置静态资源的目录</span></span><br><span class="line">        handler.setLocations(List.of(<span class="keyword">new</span> <span class="title class_">ClassPathResource</span>(<span class="string">&quot;static/&quot;</span>)));</span><br><span class="line">        <span class="keyword">return</span> handler;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 请求的img下的路径都会被映射到资源目录下</span></span><br><span class="line"><span class="comment">     * 例如访问/img/1.jpg、/img/2.jpg、/img/3.jpg</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean(&quot;/img/**&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ResourceHttpRequestHandler <span class="title function_">handler2</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ResourceHttpRequestHandler</span> <span class="variable">handler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ResourceHttpRequestHandler</span>();</span><br><span class="line">        handler.setLocations(List.of(<span class="keyword">new</span> <span class="title class_">ClassPathResource</span>(<span class="string">&quot;images/&quot;</span>)));</span><br><span class="line">        <span class="keyword">return</span> handler;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>编写启动类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A35</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">AnnotationConfigServletWebServerApplicationContext</span> <span class="variable">context</span></span><br><span class="line">                <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AnnotationConfigServletWebServerApplicationContext</span>(WebConfig.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，通过浏览器访问localhost:8080&#x2F;index.html，浏览器输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">我是欢迎页！</span><br></pre></td></tr></table></figure>

<p>运行启动类，通过浏览器访问<a target="_blank" rel="noopener" href="http://localhost:8080/img/1.jpg%EF%BC%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E8%83%BD%E6%AD%A3%E7%A1%AE%E5%B1%95%E7%A4%BA%E5%9B%BE%E7%89%87%E3%80%82">http://localhost:8080/img/1.jpg，浏览器能正确展示图片。</a></p>
<p><code>ResourceHttpRequestHandler</code>中的<code>afterPropertiesSet()</code>会添加资源解析器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterPropertiesSet</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="built_in">this</span>.resolveResourceLocations();</span><br><span class="line">    <span class="keyword">if</span> (logger.isWarnEnabled() &amp;&amp; CollectionUtils.isEmpty(<span class="built_in">this</span>.getLocations())) &#123;</span><br><span class="line">        logger.warn(<span class="string">&quot;Locations list is empty. No resources will be served unless a custom ResourceResolver is configured as an alternative to PathResourceResolver.&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.resourceResolvers.isEmpty()) &#123;</span><br><span class="line">        <span class="built_in">this</span>.resourceResolvers.add(<span class="keyword">new</span> <span class="title class_">PathResourceResolver</span>());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">this</span>.initAllowedLocations();</span><br><span class="line">    <span class="built_in">this</span>.resolverChain = <span class="keyword">new</span> <span class="title class_">DefaultResourceResolverChain</span>(<span class="built_in">this</span>.resourceResolvers);</span><br><span class="line">    <span class="built_in">this</span>.transformerChain = <span class="keyword">new</span> <span class="title class_">DefaultResourceTransformerChain</span>(<span class="built_in">this</span>.resolverChain, <span class="built_in">this</span>.resourceTransformers);</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.resourceHttpMessageConverter == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.resourceHttpMessageConverter = <span class="keyword">new</span> <span class="title class_">ResourceHttpMessageConverter</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.resourceRegionHttpMessageConverter == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.resourceRegionHttpMessageConverter = <span class="keyword">new</span> <span class="title class_">ResourceRegionHttpMessageConverter</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">ContentNegotiationManager</span> <span class="variable">manager</span> <span class="operator">=</span> <span class="built_in">this</span>.getContentNegotiationManager();</span><br><span class="line">    <span class="keyword">if</span> (manager != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.setMediaTypes(manager.getMediaTypeMappings());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">PathExtensionContentNegotiationStrategy</span> <span class="variable">strategy</span> <span class="operator">=</span> <span class="built_in">this</span>.initContentNegotiationStrategy();</span><br><span class="line">    <span class="keyword">if</span> (strategy != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.setMediaTypes(strategy.getMediaTypes());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>它会判断<code>resourceResolvers</code>是否为空，如果为空则添加<code>PathResourceResolver</code>解析器，也就是进行路径资源解析。<code>Spring</code>中提供多种解析器，我们可以手动添加增强<code>ResourceHttpRequestHandler</code>的功能。</p>
<p>我们在<code>ResourceHttpRequestHandler</code>中添加更多的解析器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean(&quot;/**&quot;)</span></span><br><span class="line"><span class="keyword">public</span> ResourceHttpRequestHandler <span class="title function_">handler1</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">ResourceHttpRequestHandler</span> <span class="variable">handler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ResourceHttpRequestHandler</span>();</span><br><span class="line">    <span class="comment">// 设置静态资源的目录</span></span><br><span class="line">    handler.setLocations(List.of(<span class="keyword">new</span> <span class="title class_">ClassPathResource</span>(<span class="string">&quot;static/&quot;</span>)));</span><br><span class="line">    handler.setResourceResolvers(List.of(</span><br><span class="line">            <span class="comment">// 添加缓存</span></span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">CachingResourceResolver</span>(<span class="keyword">new</span> <span class="title class_">ConcurrentMapCache</span>(<span class="string">&quot;cache1&quot;</span>)),</span><br><span class="line">            <span class="comment">// 读取压缩的资源</span></span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">EncodedResourceResolver</span>(),</span><br><span class="line">            <span class="comment">// 根据路径读取资源</span></span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">PathResourceResolver</span>()</span><br><span class="line">    ));</span><br><span class="line">    <span class="keyword">return</span> handler;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>测试<code>CachingResourceResolver</code>的功能：</p>
<p>运行启动类，通过浏览器访问localhost:8080&#x2F;index.html，第一次访问走的是正常逻辑，第二次访问后台输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[TRACE] 15:01:43.385 [http-nio-8080-exec-5] o.s.web.servlet.DispatcherServlet   - GET &quot;/index.html&quot;, parameters=&#123;&#125;, headers=&#123;masked&#125; in DispatcherServlet &#x27;dispatcherServlet&#x27; </span><br><span class="line">[TRACE] 15:01:43.386 [http-nio-8080-exec-5] o.s.w.s.r.CachingResourceResolver   - Resource resolved from cache </span><br><span class="line">[TRACE] 15:01:43.386 [http-nio-8080-exec-5] o.s.w.s.r.ResourceHttpRequestHandler - Resource not modified </span><br><span class="line">[TRACE] 15:01:43.386 [http-nio-8080-exec-5] o.s.web.servlet.DispatcherServlet   - No view rendering, null ModelAndView returned. </span><br><span class="line">[DEBUG] 15:01:43.386 [http-nio-8080-exec-5] o.s.web.servlet.DispatcherServlet   - Completed 304 NOT_MODIFIED, headers=&#123;masked&#125; </span><br></pre></td></tr></table></figure>

<p>发现使用了<code>CachingResourceResolver</code>中的缓存。</p>
<p>测试<code>EncodedResourceResolver</code>的功能：</p>
<p><code>EncodedResourceResolver</code>可以读取压缩的文件，提高网络传输的速度，使用此解析器我们需要手动生成压缩文件：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@PostConstruct</span></span><br><span class="line"><span class="meta">@SuppressWarnings(&quot;all&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">initGzip</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">Resource</span> <span class="variable">resource</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassPathResource</span>(<span class="string">&quot;static&quot;</span>);</span><br><span class="line">    <span class="type">File</span> <span class="variable">dir</span> <span class="operator">=</span> resource.getFile();</span><br><span class="line">    <span class="keyword">for</span> (File file : dir.listFiles(pathname -&gt; pathname.getName().endsWith(<span class="string">&quot;.html&quot;</span>))) &#123;</span><br><span class="line">        System.out.println(file);</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">FileInputStream</span> <span class="variable">fis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(file); <span class="type">GZIPOutputStream</span> <span class="variable">fos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GZIPOutputStream</span>(<span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(file.getAbsoluteFile() + <span class="string">&quot;.gz&quot;</span>))) &#123;</span><br><span class="line">            <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">8</span> * <span class="number">1024</span>];</span><br><span class="line">            <span class="type">int</span> len;</span><br><span class="line">            <span class="keyword">while</span> ((len = fis.read(bytes)) != -<span class="number">1</span>) &#123;</span><br><span class="line">                fos.write(bytes, <span class="number">0</span>, len);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行启动类，可以发现压缩后的.gz文件比原始的html文件要小很多，浏览器请求时会自动请求.gz文件，并进行解压。</p>
<p><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/11.png" alt="img"></p>
<p>如果我们访问<a target="_blank" rel="noopener" href="http://localhost:8080/%EF%BC%8C%E6%A0%B9%E8%B7%AF%E5%BE%84%E4%B8%8D%E5%AF%B9%E5%BA%94%E4%BB%BB%E4%BD%95%E8%B5%84%E6%BA%90%EF%BC%8C%E5%9B%A0%E6%AD%A4%E4%BC%9A%E8%BF%94%E5%9B%9E404%E3%80%82%E4%B8%80%E8%88%AC%E6%83%85%E5%86%B5%E4%B8%8B%E6%88%91%E4%BB%AC%E5%B8%8C%E6%9C%9B%E8%AE%BF%E9%97%AE%E6%A0%B9%E8%B7%AF%E5%BE%84%E8%83%BD%E5%A4%9F%E8%B7%B3%E8%BD%AC%E5%88%B0%E6%AC%A2%E8%BF%8E%E9%A1%B5%EF%BC%8C%E6%9C%89%E4%B8%A4%E7%A7%8D%E6%96%B9%E5%BC%8F%E5%8F%AF%E4%BB%A5%E5%AE%9E%E7%8E%B0%EF%BC%9A">http://localhost:8080/，根路径不对应任何资源，因此会返回404。一般情况下我们希望访问根路径能够跳转到欢迎页，有两种方式可以实现：</a></p>
<ol>
<li>使用静态资源作为欢迎页</li>
<li>使用控制器映射作为欢迎页</li>
</ol>
<p>这里演示使用静态资源作为欢迎页，另外，欢迎页的功能是由<code>Spring</code>提供的，在传统的<code>SpringMVC</code>中没有此功能。</p>
<p>我们在配置类中添加<code>Bean</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> WelcomePageHandlerMapping <span class="title function_">welcomePageHandlerMapping</span><span class="params">(ApplicationContext context)</span> &#123;</span><br><span class="line">    <span class="type">Resource</span> <span class="variable">resource</span> <span class="operator">=</span> context.getResource(<span class="string">&quot;classpath:static/index.html&quot;</span>);</span><br><span class="line">    <span class="comment">// 它将会生成一个处理器，这个处理器实现了Controller接口的，因此需要ControllerHandlerAdapter去调用</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">WelcomePageHandlerMapping</span>(<span class="literal">null</span>, context, resource, <span class="string">&quot;/**&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> SimpleControllerHandlerAdapter <span class="title function_">simpleControllerHandlerAdapter</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">SimpleControllerHandlerAdapter</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>welcomePageHandlerMapping</code>用来映射到欢迎页，<code>SimpleControllerHandlerAdapter</code>用来调用<code>welcomePageHandlerMapping</code>生成的处理器。</p>
<p>运行启动类，通过浏览器访问localhost:8080，浏览器输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">我是欢迎页！</span><br></pre></td></tr></table></figure>

<p>说明映射已经生效。</p>
<p>欢迎页的处理流程：</p>
<ol>
<li><code>WelcomePageHandlerMapping</code>，映射欢迎页(即只映射<code>&#39;/&#39;</code>)<ol>
<li>它内置了<code>handler</code>，<code>ParameterizableViewController</code>作用是不执行逻辑, 仅根据视图名找视图</li>
<li>视图名固定为<code>forward:index.html </code></li>
</ol>
</li>
<li><code>SimpleControllerHandlerAdapter</code>调用<code>handler</code><ol>
<li>转发至<code>/index.html</code></li>
<li>处理<code>/index.html</code>又会走静态资源处理流程</li>
</ol>
</li>
</ol>
<p><strong>五大映射器与四大处理器总结：</strong></p>
<ol>
<li><code>HandlerMapping</code>负责建立请求与控制器之间的映射关系<ol>
<li><code>RequestMappingHandlerMapping</code>：处理与<code>@RequestMapping</code>注解的匹配</li>
<li><code>WelcomePageHandlerMapping</code>：处理<code>/</code>路径，主要用于欢迎页的处理</li>
<li><code>BeanNameUrlHandlerMapping</code>：处理路径与<code>bean</code>的名字匹配，需要以<code>/</code>开头</li>
<li><code>RouterFunctionMapping</code>：支持函数式调用，<code>RequestPredicate</code>、<code>HandlerFunction</code></li>
<li>SimpleUrlHandlerMapping：处理与静态资源的映射。</li>
</ol>
</li>
</ol>
<blockquote>
<p>之间也会有顺序问题, <code>springboot</code>中默认顺序如上</p>
</blockquote>
<ol>
<li><code>HandlerAdapter</code>负责实现对各种各样的<code>handler</code>的适配调用<ol>
<li><code>RequestMappingHandlerAdapter</code>：处理<code>@RequestMapping</code>方法，参数解析器、返回值处理器体现了组合模式</li>
<li><code>SimpleControllerHandlerAdapter</code>：处理<code>Controller</code>接口</li>
<li><code>HandlerFunctionAdapter</code>：处理<code>HandlerFunction</code>函数式接口</li>
<li><code>HttpRequestHandlerAdapter</code>：处理<code>HttpRequestHandler</code>接口，(静态资源处理)这也是典型适配器模式体现</li>
</ol>
</li>
<li><code>ResourceHttpRequestHandler.setResourceResolvers</code>这是典型责任链模式体现</li>
</ol>
<h1 id="SpringMVC处理流程"><a href="#SpringMVC处理流程" class="headerlink" title="SpringMVC处理流程"></a>SpringMVC处理流程</h1><p>当浏览器发送一个请求 <code>http://localhost:8080/hello</code> 后，请求到达服务器，其处理流程是：</p>
<ol>
<li>服务器提供了<code>DispatcherServlet</code>，它使用的是标准<code>Servlet</code>技术<ol>
<li>路径：默认映射路径为<code>/</code>，即会匹配到所有请求<code>URL</code>，可作为请求的统一入口，也被称之为<strong>前控制器</strong><ul>
<li><code>jsp</code>不会匹配到<code>DispatcherServlet</code></li>
<li>其它有路径的<code>Servlet</code>匹配优先级也高于<code>DispatcherServlet</code></li>
</ul>
</li>
<li>创建：在<code>SpringBoot</code>中，由<code>DispatcherServletAutoConfiguration</code>这个自动配置类提供 <code>DispatcherServlet</code>的<code>bean</code></li>
<li>初始化：<code>DispatcherServlet</code>初始化时会优先到容器里寻找各种组件，作为它的成员变量<ul>
<li><code>HandlerMapping</code>，初始化时记录映射关系</li>
<li><code>HandlerAdapter</code>，初始化时准备参数解析器、返回值处理器、消息转换器</li>
<li><code>HandlerExceptionResolver</code>，初始化时准备参数解析器、返回值处理器、消息转换器</li>
<li><code>ViewResolver</code></li>
</ul>
</li>
</ol>
</li>
<li><code>DispatcherServlet</code>会利用<code>RequestMappingHandlerMapping</code>查找控制器方法<ol>
<li>例如根据<code>/hello</code>路径找到<code>@RequestMapping(&quot;/hello&quot;)</code>对应的控制器方法</li>
<li>控制器方法会被封装为<code>HandlerMethod</code>对象，并结合匹配到的拦截器一起返回给<code>DispatcherServlet</code></li>
<li><code>HandlerMethod</code>和拦截器合在一起称为<code>HandlerExecutionChain</code>（调用链）对象</li>
</ol>
</li>
<li><code>DispatcherServlet</code>接下来会：<ol>
<li>调用拦截器的<code>preHandle</code>方法</li>
<li><code>RequestMappingHandlerAdapter</code>调用<code>handle</code>方法，准备数据绑定工厂、模型工厂、<code>ModelAndViewContainer</code>、将<code>HandlerMethod</code>完善为<code>ServletInvocableHandlerMethod</code><ol>
<li><code>@ControllerAdvice</code>全局增强点：补充模型数据</li>
<li><code>@ControllerAdvice</code>全局增强点：补充自定义类型转换器</li>
<li>使用<code>HandlerMethodArgumentResolver</code>准备参数<ul>
<li><code>@ControllerAdvice</code>全局增强点：<code>RequestBody</code>增强</li>
</ul>
</li>
<li>调用<code>ServletInvocableHandlerMethod</code></li>
<li>使用<code>HandlerMethodReturnValueHandler</code>处理返回值<ul>
<li><code>@ControllerAdvice</code>全局增强点：<code>ResponseBody</code>增强</li>
</ul>
</li>
<li>根据<code>ModelAndViewContainer</code>获取<code>ModelAndView</code><ul>
<li>如果返回的<code>ModelAndView</code>为<code>null</code>，不走第4步视图解析及渲染流程<ul>
<li>例如，有的返回值处理器调用了<code>HttpMessageConverter</code>来将结果转换为<code>JSON</code>，这时 <code>ModelAndView</code>就为<code>null</code></li>
</ul>
</li>
<li>如果返回的<code>ModelAndView</code>不为<code>null</code>，会在第4步走视图解析及渲染流程</li>
</ul>
</li>
</ol>
</li>
<li>调用拦截器的<code>postHandle</code>方法</li>
<li>处理异常或视图渲染<ol>
<li>如果1~3出现异常，走<code>ExceptionHandlerExceptionResolver</code>处理异常流程<ul>
<li><code>@ControllerAdvice</code>全局增强点：<code>@ExceptionHandler</code>异常处理</li>
</ul>
</li>
<li>正常，走视图解析及渲染流程</li>
</ol>
</li>
<li>调用拦截器的<code>afterCompletion</code>方法</li>
</ol>
</li>
</ol>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href>狼族少年、血狼</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://geekwolfman.github.io/2023/04/15/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB.html">https://geekwolfman.github.io/2023/04/15/Spring高级45讲【第四章】：WEB.html</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://geekwolfman.github.io" target="_blank">狼族少年、血狼</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/spring/">spring</a></div><div class="post_share"><div class="social-share" data-image="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%9B%9B%E7%AB%A0%E3%80%91%EF%BC%9AWEB/01cover.jpg" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2023/04/15/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E4%BA%94%E7%AB%A0%E3%80%91%EF%BC%9ABOOT.html" title="Spring高级45讲【第五章】：BOOT"><img class="cover" src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E4%BA%94%E7%AB%A0%E3%80%91%EF%BC%9ABOOT/01cover.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Spring高级45讲【第五章】：BOOT</div></div></a></div><div class="next-post pull-right"><a href="/2023/04/15/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E4%B8%89%E7%AB%A0%E3%80%91%EF%BC%9AAOP.html" title="Spring高级45讲【第三章】：AOP"><img class="cover" src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E4%B8%89%E7%AB%A0%E3%80%91%EF%BC%9AAOP/01cover.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">Spring高级45讲【第三章】：AOP</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/2023/04/15/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%85%AD%E7%AB%A0%E3%80%91%EF%BC%9AOTHER.html" title="Spring高级45讲【第六章】：OTHER"><img class="cover" src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E5%85%AD%E7%AB%A0%E3%80%91%EF%BC%9AOTHER/00cover.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2023-04-15</div><div class="title">Spring高级45讲【第六章】：OTHER</div></div></a></div><div><a href="/2023/04/15/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E4%BA%94%E7%AB%A0%E3%80%91%EF%BC%9ABOOT.html" title="Spring高级45讲【第五章】：BOOT"><img class="cover" src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E4%BA%94%E7%AB%A0%E3%80%91%EF%BC%9ABOOT/01cover.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2023-04-15</div><div class="title">Spring高级45讲【第五章】：BOOT</div></div></a></div><div><a href="/2023/04/15/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E4%B8%89%E7%AB%A0%E3%80%91%EF%BC%9AAOP.html" title="Spring高级45讲【第三章】：AOP"><img class="cover" src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E4%B8%89%E7%AB%A0%E3%80%91%EF%BC%9AAOP/01cover.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2023-04-15</div><div class="title">Spring高级45讲【第三章】：AOP</div></div></a></div><div><a href="/2023/04/15/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E4%BA%8C%E7%AB%A0%E3%80%91%EF%BC%9AIOC.html" title="Spring高级45讲【第二章】：IOC"><img class="cover" src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E4%BA%8C%E7%AB%A0%E3%80%91%EF%BC%9AIOC/01cover.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2023-04-15</div><div class="title">Spring高级45讲【第二章】：IOC</div></div></a></div><div><a href="/2023/04/15/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E4%B8%80%E7%AB%A0%E3%80%91%EF%BC%9A%E5%AF%BC%E5%AD%A6.html" title="Spring高级45讲【第一章】：导学"><img class="cover" src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/Spring%E9%AB%98%E7%BA%A745%E8%AE%B2%E3%80%90%E7%AC%AC%E4%B8%80%E7%AB%A0%E3%80%91%EF%BC%9A%E5%AF%BC%E5%AD%A6/01cover.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2023-04-15</div><div class="title">Spring高级45讲【第一章】：导学</div></div></a></div><div><a href="/2023/03/23/%E6%89%8B%E5%86%99%E7%AE%80%E6%98%93%E7%89%88spring-web%E6%A1%86%E6%9E%B6.html" title="手写简易版spring web框架"><img class="cover" src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/articles/%E6%89%8B%E5%86%99%E7%AE%80%E6%98%93%E7%89%88spring-web%E6%A1%86%E6%9E%B6/01cover.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2023-03-23</div><div class="title">手写简易版spring web框架</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src="https://geekwolfman-blog.oss-cn-chengdu.aliyuncs.com/config/avatar/avatar.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">狼族少年、血狼</div><div class="author-info__description"></div></div><div class="card-info-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">57</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">14</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">9</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://wpa.qq.com/msgrd?v=3&amp;uin=2370032534&amp;site=qq&amp;menu=yes&amp;jumpflag=1"><i class="fa-brands fa-qq"></i><span>添加博主QQ</span></a></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content">本站所有博文均是博主的学习笔记与个人理解，来源于网络，如有<span style="color:red;font-weight:bold;">侵权</span>请<a target="_blank" rel="noopener" href="https://wpa.qq.com/msgrd?v=3&uin=2370032534&site=qq&menu=yes&jumpflag=1" style="color:#49B1F5;font-weight:bold">联系我</a>进行删除🥰。</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#DispatcherServlet%E5%8F%8A%E5%85%B6%E9%87%8D%E8%A6%81%E7%BB%84%E4%BB%B6"><span class="toc-number">1.</span> <span class="toc-text">DispatcherServlet及其重要组件</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#DispatcherServlet%E5%88%9D%E5%A7%8B%E5%8C%96"><span class="toc-number">1.1.</span> <span class="toc-text">DispatcherServlet初始化</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#DispatcherServlet%E5%88%9D%E5%A7%8B%E5%8C%96%E8%BF%87%E7%A8%8B"><span class="toc-number">1.2.</span> <span class="toc-text">DispatcherServlet初始化过程</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%8F%82%E6%95%B0%E8%A7%A3%E6%9E%90%E5%99%A8"><span class="toc-number">2.</span> <span class="toc-text">参数解析器</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%8F%82%E6%95%B0%E5%90%8D%E8%A7%A3%E6%9E%90"><span class="toc-number">3.</span> <span class="toc-text">参数名解析</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%82%E6%95%B0%E5%90%8D%E7%A7%B0%E5%8E%9F%E7%90%86"><span class="toc-number">3.1.</span> <span class="toc-text">参数名称原理</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%8E%B7%E5%8F%96%E5%8F%82%E6%95%B0%E5%90%8D%E7%A7%B0"><span class="toc-number">3.2.</span> <span class="toc-text">获取参数名称</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%AF%B9%E8%B1%A1%E7%BB%91%E5%AE%9A%E4%B8%8E%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2"><span class="toc-number">4.</span> <span class="toc-text">对象绑定与类型转换</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%BA%95%E5%B1%82%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E6%8E%A5%E5%8F%A3"><span class="toc-number">4.1.</span> <span class="toc-text">底层类型转换接口</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%AB%98%E7%BA%A7%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E6%8E%A5%E5%8F%A3"><span class="toc-number">4.2.</span> <span class="toc-text">高级类型转换接口</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A%E7%A4%BA%E4%BE%8B"><span class="toc-number">4.3.</span> <span class="toc-text">类型转换与数据绑定示例</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#SimpleTypeConverter"><span class="toc-number">4.3.1.</span> <span class="toc-text">SimpleTypeConverter</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#BeanWrapperImpl"><span class="toc-number">4.3.2.</span> <span class="toc-text">BeanWrapperImpl</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#DirectFieldAccessor"><span class="toc-number">4.3.3.</span> <span class="toc-text">DirectFieldAccessor</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#DataBinder"><span class="toc-number">4.3.4.</span> <span class="toc-text">DataBinder</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A%E5%B7%A5%E5%8E%82"><span class="toc-number">4.4.</span> <span class="toc-text">数据绑定工厂</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%8E%B7%E5%8F%96%E6%B3%9B%E5%9E%8B%E5%8F%82%E6%95%B0"><span class="toc-number">4.5.</span> <span class="toc-text">获取泛型参数</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#InitBinder%E6%B3%A8%E8%A7%A3"><span class="toc-number">5.</span> <span class="toc-text">@InitBinder注解</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%8E%A7%E5%88%B6%E5%99%A8%E6%96%B9%E6%B3%95%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B"><span class="toc-number">6.</span> <span class="toc-text">控制器方法执行流程</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#ModelAttribute%E6%B3%A8%E8%A7%A3"><span class="toc-number">7.</span> <span class="toc-text">@ModelAttribute注解</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E8%BF%94%E5%9B%9E%E5%80%BC%E5%A4%84%E7%90%86%E5%99%A8"><span class="toc-number">8.</span> <span class="toc-text">返回值处理器</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#MessageConverter"><span class="toc-number">9.</span> <span class="toc-text">MessageConverter</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#ResponseBodyAdvice"><span class="toc-number">10.</span> <span class="toc-text">ResponseBodyAdvice</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%BC%82%E5%B8%B8%E8%A7%A3%E6%9E%90%E5%99%A8"><span class="toc-number">11.</span> <span class="toc-text">异常解析器</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#ExceptionHandler%E6%B3%A8%E8%A7%A3"><span class="toc-number">12.</span> <span class="toc-text">@ExceptionHandler注解</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Tomcat%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86"><span class="toc-number">13.</span> <span class="toc-text">Tomcat异常处理</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#BeanNameUrlHandlerMapping%E4%B8%8ESimpleControllerHandlerAdapter"><span class="toc-number">14.</span> <span class="toc-text">BeanNameUrlHandlerMapping与SimpleControllerHandlerAdapter</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89HandlerMapping%E4%B8%8EHandlerAdapter"><span class="toc-number">15.</span> <span class="toc-text">自定义HandlerMapping与HandlerAdapter</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#RouterFunctionMapping%E4%B8%8EHandlerFunctionAdapter"><span class="toc-number">16.</span> <span class="toc-text">RouterFunctionMapping与HandlerFunctionAdapter</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#SimpleUrlHandlerMapping%E4%B8%8EHttpRequestHandlerAdapter"><span class="toc-number">17.</span> <span class="toc-text">SimpleUrlHandlerMapping与HttpRequestHandlerAdapter</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#SpringMVC%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B"><span class="toc-number">18.</span> <span class="toc-text">SpringMVC处理流程</span></a></li></ol></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2023 By 狼族少年、血狼</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.min.js"></script><div class="js-pjax"></div><script defer="defer" id="ribbon" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/canvas-ribbon.min.js" size="150" alpha="0.6" zIndex="-1" mobile="false" data-click="false"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div><script src="/js/search/local-search.js"></script></div></body></html>